Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a29290aadc | ||
|
|
3b61b80598 | ||
|
|
d6346b37e6 | ||
|
|
451f3983e0 | ||
|
|
75525b107a | ||
|
|
10f2c66bb8 | ||
|
|
a58e5fb014 | ||
|
|
fa049a5ea4 | ||
|
|
7e54996527 | ||
|
|
956ba2a167 | ||
|
|
6d30d40b86 | ||
|
|
6ccba22b91 | ||
|
|
63c5ae8797 | ||
|
|
68ba79add6 | ||
|
|
bbbcb175c9 | ||
|
|
062ad09ae0 | ||
|
|
521486d405 | ||
|
|
2a7a96d566 | ||
|
|
b8e54d131b | ||
|
|
51f6b48b98 | ||
|
|
b39ed27543 | ||
|
|
5ce213862d | ||
|
|
ed4f96b1d4 | ||
|
|
e7ed787ab0 | ||
|
|
90ac06a675 | ||
|
|
b7cd5e9a2d | ||
|
|
d65284fb80 | ||
|
|
1c7759738e | ||
|
|
4076b99ff7 | ||
|
|
a360b6a1b8 | ||
|
|
711259beef | ||
|
|
b80eb18510 | ||
|
|
58b32031be | ||
|
|
3067586803 | ||
|
|
f36d5b4856 | ||
|
|
1326c4ea15 | ||
|
|
1af0823d65 | ||
|
|
09f3e50be0 | ||
|
|
9ba3393afb | ||
|
|
0268b4beb5 | ||
|
|
b41b5c210d | ||
|
|
2e4c61a5c8 | ||
|
|
86d2318fed | ||
|
|
1805f8e81e | ||
|
|
588d38d127 | ||
|
|
75243b4b84 | ||
|
|
f6ab2b7ccb | ||
|
|
7e5c3eac12 | ||
|
|
2a9b9cb97c | ||
|
|
249fdf10f3 | ||
|
|
8fb436649c | ||
|
|
85a8e21bf8 | ||
|
|
c9c89db93e | ||
|
|
68097d71be | ||
|
|
850ab5b7b0 | ||
|
|
e057feff9b | ||
|
|
f6e75f8060 | ||
|
|
42f3b4697a | ||
|
|
12227a4534 | ||
|
|
f280d3f5b9 | ||
|
|
6c204f13f3 | ||
|
|
63986ae152 | ||
|
|
d9a3b29534 | ||
|
|
544a49c6d4 | ||
|
|
041c5c79ff | ||
|
|
6f787e68bb | ||
|
|
dc2085a694 | ||
|
|
fcc34bb48a | ||
|
|
2f68f3612a | ||
|
|
b797e8b105 | ||
|
|
7d30ddfef8 | ||
|
|
4135c0bed2 | ||
|
|
4bbba36259 | ||
|
|
cec36b5133 | ||
|
|
bef0ff7f4f | ||
|
|
e597d7fdcb | ||
|
|
67bf36d738 | ||
|
|
4130b6ed23 | ||
|
|
6b69e62a71 | ||
|
|
31fe7a9a5d | ||
|
|
818b8fe7de | ||
|
|
d2df0107a2 | ||
|
|
3e9fe20ddc | ||
|
|
0e0c63bd6d | ||
|
|
460271895c | ||
|
|
ae079aa000 | ||
|
|
1a31513a7f | ||
|
|
43e638611a | ||
|
|
3b9ba0e43d | ||
|
|
5e0d7c40a5 | ||
|
|
30615ed61d | ||
|
|
3bd6916f48 | ||
|
|
bd61109358 | ||
|
|
1f0515018c | ||
|
|
1c0ceacfd7 | ||
|
|
aa5fd5d587 | ||
|
|
af010c59f5 | ||
|
|
aca4e69042 | ||
|
|
bb4cde4f28 | ||
|
|
f25e16e509 | ||
|
|
2131f3efba | ||
|
|
f722e43b4d | ||
|
|
27ac073bba | ||
|
|
d7274473fc | ||
|
|
af9ea12a55 | ||
|
|
9bfb161516 | ||
|
|
e30b974629 | ||
|
|
41ebc1ce0b | ||
|
|
f2ced177f0 | ||
|
|
c8567d0a1a | ||
|
|
efd165d83c | ||
|
|
ff02be904b | ||
|
|
28fa800453 | ||
|
|
387f1b6192 | ||
|
|
99be726e95 | ||
|
|
eb8e63a3d4 | ||
|
|
840878574d | ||
|
|
69819ff7a5 | ||
|
|
0d01e58038 | ||
|
|
dcf7dd31d3 | ||
|
|
ebde6b6271 | ||
|
|
2fe5faf089 | ||
|
|
ea09e9abc2 | ||
|
|
92348510d9 | ||
|
|
75f5ff0031 | ||
|
|
74f0005901 | ||
|
|
b90c902e37 | ||
|
|
56f64d5fd3 | ||
|
|
0db6a2805e |
+13
@@ -0,0 +1,13 @@
|
||||
[run]
|
||||
source = .
|
||||
include = *.py
|
||||
omit =
|
||||
*migrations*
|
||||
*tests*
|
||||
*.html
|
||||
*whoosh_cn_backend*
|
||||
*apps*
|
||||
*commands*
|
||||
*oauth*
|
||||
*travis_test*
|
||||
*settings.py*
|
||||
@@ -72,3 +72,6 @@ google93fd32dbd906620a.html
|
||||
baidu_verify_FlHL7cUyC9.html
|
||||
BingSiteAuth.xml
|
||||
cb9339dbe2ff86a5aa169d28dba5f615.txt
|
||||
werobot_session
|
||||
django.jpg
|
||||
uploads/
|
||||
+7
-7
@@ -1,31 +1,31 @@
|
||||
dist: precise
|
||||
language: python
|
||||
python:
|
||||
- "3.4"
|
||||
- "3.5"
|
||||
- "3.6"
|
||||
services:
|
||||
- mysql
|
||||
env:
|
||||
global:
|
||||
- DJANGO_SETTINGS_MODULE="test.travis_settings"
|
||||
- DJANGO_SETTINGS_MODULE="travis_test.travis_settings"
|
||||
matrix:
|
||||
- DJANGO="Django==1.8"
|
||||
- DJANGO="Django==1.9"
|
||||
- DJANGO="Django==1.10"
|
||||
- DJANGO="Django==1.11"
|
||||
branches:
|
||||
only:
|
||||
- master
|
||||
# command to install dependencies
|
||||
install:
|
||||
- pip install -r test/requirements.txt
|
||||
- pip install -r travis_test/requirements.txt
|
||||
- pip install "$DJANGO"
|
||||
- pip install python-coveralls
|
||||
- pip install coverage
|
||||
before_script:
|
||||
- mysql -e 'CREATE DATABASE `djangoblog` /*!40100 DEFAULT CHARACTER SET utf8 */;'
|
||||
- python manage.py makemigrations accounts
|
||||
- python manage.py makemigrations blog
|
||||
- python manage.py makemigrations comments
|
||||
- python manage.py makemigrations oauth
|
||||
- python manage.py makemigrations
|
||||
- python manage.py migrate
|
||||
- python manage.py collectstatic --noinput
|
||||
- python manage.py compress --force
|
||||
@@ -33,4 +33,4 @@ before_script:
|
||||
script:
|
||||
- coverage run manage.py test
|
||||
after_success:
|
||||
- coveralls
|
||||
- coveralls
|
||||
|
||||
@@ -0,0 +1,100 @@
|
||||
#!/usr/bin/env python
|
||||
# encoding: utf-8
|
||||
|
||||
|
||||
"""
|
||||
@version: ??
|
||||
@author: liangliangyy
|
||||
@license: MIT Licence
|
||||
@contact: liangliangyy@gmail.com
|
||||
@site: https://www.lylinux.org/
|
||||
@software: PyCharm
|
||||
@file: blog_signals.py
|
||||
@time: 2017/8/12 上午10:18
|
||||
"""
|
||||
|
||||
import django.dispatch
|
||||
from django.dispatch import receiver
|
||||
from django.conf import settings
|
||||
from DjangoBlog.utils import cache, send_email, expire_view_cache, logger
|
||||
from DjangoBlog.spider_notify import SpiderNotify
|
||||
from django.contrib.sites.models import Site
|
||||
|
||||
comment_save_signal = django.dispatch.Signal(providing_args=["comment_id", "username", "serverport"])
|
||||
article_save_signal = django.dispatch.Signal(providing_args=['id', 'is_update_views'])
|
||||
user_login_logout_signal = django.dispatch.Signal(providing_args=['id', 'type'])
|
||||
|
||||
|
||||
@receiver(article_save_signal)
|
||||
def article_save_callback(sender, **kwargs):
|
||||
id = kwargs['id']
|
||||
is_update_views = kwargs['is_update_views']
|
||||
type = sender.__name__
|
||||
obj = None
|
||||
from blog.models import Article, Category, Tag
|
||||
if type == 'Article':
|
||||
obj = Article.objects.get(id=id)
|
||||
elif type == 'Category':
|
||||
obj = Category.objects.get(id=id)
|
||||
elif type == 'Tag':
|
||||
obj = Tag.objects.get(id=id)
|
||||
if obj is not None:
|
||||
if not settings.TESTING and not is_update_views:
|
||||
try:
|
||||
notify_url = obj.get_full_url()
|
||||
SpiderNotify.baidu_notify([notify_url])
|
||||
except Exception as ex:
|
||||
logger.error("notify sipder", ex)
|
||||
print(ex)
|
||||
|
||||
|
||||
@receiver(comment_save_signal)
|
||||
def comment_save_callback(sender, **kwargs):
|
||||
from comments.models import Comment
|
||||
|
||||
serverport = kwargs['serverport']
|
||||
username = kwargs['username']
|
||||
comment = Comment.objects.get(id=kwargs['comment_id'])
|
||||
site = Site.objects.get_current().domain
|
||||
article = comment.article
|
||||
# if not settings.DEBUG:
|
||||
if True:
|
||||
subject = '感谢您发表的评论'
|
||||
article_url = "https://{site}{path}".format(site=site, path=comment.article.get_absolute_url())
|
||||
html_content = """
|
||||
<p>非常感谢您在本站发表评论</p>
|
||||
您可以访问
|
||||
<a href="%s" rel="bookmark">%s</a>
|
||||
来查看您的评论,
|
||||
再次感谢您!
|
||||
<br />
|
||||
如果上面链接无法打开,请将此链接复制至浏览器。
|
||||
%s
|
||||
""" % (article_url, comment.article.title, article_url)
|
||||
tomail = comment.author.email
|
||||
send_email([tomail], subject, html_content)
|
||||
|
||||
if comment.parent_comment:
|
||||
html_content = """
|
||||
您在 <a href="%s" rel="bookmark">%s</a> 的评论 <br/> %s <br/> 收到回复啦.快去看看吧
|
||||
<br/>
|
||||
如果上面链接无法打开,请将此链接复制至浏览器。
|
||||
%s
|
||||
""" % (article_url, article.title, comment.parent_comment.body, article_url)
|
||||
tomail = comment.parent_comment.author.email
|
||||
send_email([tomail], subject, html_content)
|
||||
|
||||
path = article.get_absolute_url()
|
||||
site = Site.objects.get_current().domain
|
||||
if site.find(':') > 0:
|
||||
site = site[0:site.find(':')]
|
||||
|
||||
expire_view_cache(path, servername=site, serverport=serverport, key_prefix='blogdetail')
|
||||
if cache.get('seo_processor'):
|
||||
cache.delete('seo_processor')
|
||||
comment_cache_key = 'article_comments_{id}'.format(id=article.id)
|
||||
cache.delete(comment_cache_key)
|
||||
from django.core.cache.utils import make_template_fragment_key
|
||||
|
||||
key = make_template_fragment_key('sidebar', [username])
|
||||
cache.delete(key)
|
||||
+2
-2
@@ -17,7 +17,7 @@ from django.contrib.syndication.views import Feed
|
||||
from blog.models import Article
|
||||
from django.conf import settings
|
||||
from django.utils.feedgenerator import Rss201rev2Feed
|
||||
from DjangoBlog.utils import common_markdown
|
||||
from DjangoBlog.utils import CommonMarkdown
|
||||
from django.contrib.auth import get_user_model
|
||||
from django.contrib.auth.models import User
|
||||
from django.contrib.sites.models import Site
|
||||
@@ -44,7 +44,7 @@ class DjangoBlogFeed(Feed):
|
||||
return item.title
|
||||
|
||||
def item_description(self, item):
|
||||
return common_markdown.get_markdown(item.body)
|
||||
return CommonMarkdown.get_markdown(item.body)
|
||||
|
||||
def feed_copyright(self):
|
||||
# print(Site.objects.get_current().name)
|
||||
|
||||
+31
-16
@@ -9,7 +9,7 @@ https://docs.djangoproject.com/en/1.10/topics/settings/
|
||||
For the full list of settings and their values, see
|
||||
https://docs.djangoproject.com/en/1.10/ref/settings/
|
||||
"""
|
||||
|
||||
import sys
|
||||
import os
|
||||
|
||||
# Build paths inside the project like this: os.path.join(BASE_DIR, ...)
|
||||
@@ -19,14 +19,15 @@ BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||||
# See https://docs.djangoproject.com/en/1.10/howto/deployment/checklist/
|
||||
|
||||
# SECURITY WARNING: keep the secret key used in production secret!
|
||||
SECRET_KEY = '&3g0bdza#c%dm1lf%5gi&0-*53p3t0m*hmcvo29cn^$ji7je(c'
|
||||
SECRET_KEY = os.environ.get('DJANGO_SECRET_KEY')
|
||||
|
||||
# SECURITY WARNING: don't run with debug turned on in production!
|
||||
#DEBUG = True
|
||||
# DEBUG = True
|
||||
DEBUG = False
|
||||
TESTING = len(sys.argv) > 1 and sys.argv[1] == 'test'
|
||||
|
||||
# ALLOWED_HOSTS = []
|
||||
ALLOWED_HOSTS = ['www.lylinux.net', '127.0.0.1']
|
||||
ALLOWED_HOSTS = ['www.lylinux.net', '127.0.0.1', 'example.com']
|
||||
# Application definition
|
||||
|
||||
INSTALLED_APPS = [
|
||||
@@ -44,6 +45,7 @@ INSTALLED_APPS = [
|
||||
'accounts',
|
||||
'comments',
|
||||
'oauth',
|
||||
'servermanager',
|
||||
'compressor'
|
||||
]
|
||||
|
||||
@@ -51,9 +53,9 @@ MIDDLEWARE_CLASSES = [
|
||||
'django.middleware.security.SecurityMiddleware',
|
||||
'django.contrib.sessions.middleware.SessionMiddleware',
|
||||
'django.middleware.gzip.GZipMiddleware',
|
||||
'django.middleware.cache.UpdateCacheMiddleware',
|
||||
# 'django.middleware.cache.UpdateCacheMiddleware',
|
||||
'django.middleware.common.CommonMiddleware',
|
||||
'django.middleware.cache.FetchFromCacheMiddleware',
|
||||
# 'django.middleware.cache.FetchFromCacheMiddleware',
|
||||
'django.middleware.csrf.CsrfViewMiddleware',
|
||||
'django.contrib.auth.middleware.AuthenticationMiddleware',
|
||||
'django.contrib.messages.middleware.MessageMiddleware',
|
||||
@@ -162,7 +164,7 @@ SITE_NAME = '且听风吟'
|
||||
SITE_URL = 'http://www.lylinux.net'
|
||||
SITE_DESCRIPTION = '大巧无工,重剑无锋.'
|
||||
SITE_SEO_DESCRIPTION = '小站主要用来分享和记录学习经验,教程,记录个人生活的点滴以及一些随笔.欢迎大家访问小站'
|
||||
SITE_SEO_KEYWORDS = 'linux,apache,mysql,服务器,ubuntu,shell,web,csharp,.net,asp,mac,swift'
|
||||
SITE_SEO_KEYWORDS = 'linux,apache,mysql,服务器,ubuntu,shell,web,csharp,.net,asp,mac,swift,python,django'
|
||||
ARTICLE_SUB_LENGTH = 300
|
||||
SHOW_GOOGLE_ADSENSE = False
|
||||
# bootstrap颜色样式
|
||||
@@ -184,10 +186,12 @@ CACHE_CONTROL_MAX_AGE = 2592000
|
||||
CACHES = {
|
||||
'default': {
|
||||
'BACKEND': 'django.core.cache.backends.memcached.MemcachedCache',
|
||||
'TIMEOUT': 10800,
|
||||
'LOCATION': '127.0.0.1:11211',
|
||||
},
|
||||
'locmemcache': {
|
||||
'BACKEND': 'django.core.cache.backends.locmem.LocMemCache',
|
||||
'TIMEOUT': 10800,
|
||||
'LOCATION': 'unique-snowflake',
|
||||
}
|
||||
}
|
||||
@@ -195,24 +199,34 @@ CACHE_MIDDLEWARE_SECONDS = 60 * 60 * 10
|
||||
CACHE_MIDDLEWARE_KEY_PREFIX = "djangoblog"
|
||||
CACHE_MIDDLEWARE_ALIAS = 'default'
|
||||
|
||||
SESSION_ENGINE = "django.contrib.sessions.backends.cache"
|
||||
SESSION_CACHE_ALIAS = 'default'
|
||||
# SESSION_ENGINE = "django.contrib.sessions.backends.cache"
|
||||
# SESSION_CACHE_ALIAS = 'default'
|
||||
|
||||
OAHUTH = {
|
||||
'sina': {
|
||||
'appkey': '3161614143',
|
||||
'appsecret': 'ee17c099317f872eeddb25204ea46721',
|
||||
'callbackurl': 'http://www.lylinux.net/oauth/weibo'
|
||||
'appkey': os.environ.get('SINA_APP_KEY'),
|
||||
'appsecret': os.environ.get('SINA_APP_SECRET'),
|
||||
'callbackurl': 'http://www.lylinux.net/oauth/authorize?type=weibo'
|
||||
},
|
||||
'google': {
|
||||
'appkey': os.environ.get('GOOGLE_APP_KEY'),
|
||||
'appsecret': os.environ.get('GOOGLE_APP_SECRET'),
|
||||
'callbackurl': 'http://www.lylinux.net/oauth/googleauthorize'
|
||||
'callbackurl': 'http://www.lylinux.net/oauth/authorize?type=google'
|
||||
},
|
||||
'github': {
|
||||
'appkey': os.environ.get('GITHUB_APP_KEY'),
|
||||
'appsecret': os.environ.get('GITHUB_APP_SECRET'),
|
||||
'callbackurl': 'http://www.lylinux.net/oauth/authorize?type=github'
|
||||
},
|
||||
'facebook': {
|
||||
'appkey': os.environ.get('FACEBOOK_APP_KEY'),
|
||||
'appsecret': os.environ.get('FACEBOOK_APP_SECRET'),
|
||||
'callbackurl': 'http://www.lylinux.net/oauth/authorize?type=facebook'
|
||||
}
|
||||
}
|
||||
|
||||
SITE_ID = 1
|
||||
BAIDU_NOTIFY_URL = "http://data.zz.baidu.com/urls?site=https://www.lylinux.net&token=1uAOGrMsUm5syDGn&type=original"
|
||||
BAIDU_NOTIFY_URL = "http://data.zz.baidu.com/urls?site=https://www.lylinux.net&token=1uAOGrMsUm5syDGn"
|
||||
|
||||
# Emial:
|
||||
EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend'
|
||||
@@ -220,7 +234,7 @@ EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend'
|
||||
EMAIL_USE_TLS = True
|
||||
# EMAIL_USE_SSL = True
|
||||
|
||||
EMAIL_HOST = 'smtp.exmail.qq.com'
|
||||
EMAIL_HOST = 'smtp.zoho.com'
|
||||
EMAIL_PORT = 587
|
||||
EMAIL_HOST_USER = os.environ.get('DJANGO_EMAIL_USER')
|
||||
EMAIL_HOST_PASSWORD = os.environ.get('DJANGO_EMAIL_PASSWORD')
|
||||
@@ -228,7 +242,8 @@ DEFAULT_FROM_EMAIL = EMAIL_HOST_USER
|
||||
SERVER_EMAIL = os.environ.get('DJANGO_EMAIL_USER')
|
||||
# 设置debug=false 未处理异常邮件通知
|
||||
ADMINS = [('liangliang', 'liangliangyy@gmail.com')]
|
||||
|
||||
# 微信管理员密码(两次md5获得)
|
||||
WXADMIN = '995F03AC401D6CABABAEF756FC4D43C7'
|
||||
LOGGING = {
|
||||
'version': 1,
|
||||
'disable_existing_loggers': False,
|
||||
|
||||
@@ -19,6 +19,7 @@ from accounts.models import BlogUser
|
||||
from django.contrib.sitemaps import GenericSitemap
|
||||
from django.core.urlresolvers import reverse
|
||||
|
||||
|
||||
class StaticViewSitemap(Sitemap):
|
||||
priority = 0.5
|
||||
changefreq = 'daily'
|
||||
@@ -68,7 +69,7 @@ class UserSiteMap(Sitemap):
|
||||
priority = "0.3"
|
||||
|
||||
def items(self):
|
||||
return BlogUser.objects.all()
|
||||
return list(set(map(lambda x: x.author, Article.objects.all())))
|
||||
|
||||
def lastmod(self, obj):
|
||||
return obj.date_joined
|
||||
|
||||
@@ -18,7 +18,7 @@ import requests
|
||||
from django.conf import settings
|
||||
|
||||
|
||||
class spider_notify():
|
||||
class SpiderNotify():
|
||||
@staticmethod
|
||||
def baidu_notify(urls):
|
||||
try:
|
||||
@@ -36,7 +36,7 @@ class spider_notify():
|
||||
print(e)
|
||||
|
||||
@staticmethod
|
||||
def notify(self, url):
|
||||
def notify(url):
|
||||
|
||||
spider_notify.baidu_notify(url)
|
||||
spider_notify.__google_notify()
|
||||
SpiderNotify.baidu_notify(url)
|
||||
SpiderNotify.__google_notify()
|
||||
|
||||
@@ -0,0 +1,53 @@
|
||||
#!/usr/bin/env python
|
||||
# encoding: utf-8
|
||||
|
||||
|
||||
"""
|
||||
@version: ??
|
||||
@author: liangliangyy
|
||||
@license: MIT Licence
|
||||
@contact: liangliangyy@gmail.com
|
||||
@site: https://www.lylinux.net/
|
||||
@software: PyCharm
|
||||
@file: tests.py
|
||||
@time: 2017/10/25 下午10:16
|
||||
"""
|
||||
|
||||
from django.test import Client, RequestFactory, TestCase
|
||||
from blog.models import Article, Category, Tag
|
||||
from django.contrib.auth import get_user_model
|
||||
from django.contrib.sites.models import Site
|
||||
from django.core.urlresolvers import reverse
|
||||
import datetime
|
||||
from DjangoBlog.utils import *
|
||||
|
||||
|
||||
class DjangoBlogTest(TestCase):
|
||||
def setUp(self):
|
||||
pass
|
||||
|
||||
def test_utils(self):
|
||||
md5 = get_md5('test')
|
||||
self.assertIsNotNone(md5)
|
||||
c = CommonMarkdown.get_markdown('''
|
||||
# Title1
|
||||
|
||||
```python
|
||||
import os
|
||||
```
|
||||
|
||||
[url](https://www.lylinux.org/)
|
||||
|
||||
[ddd](http://www.baidu.com)
|
||||
|
||||
|
||||
''')
|
||||
self.assertIsNotNone(c)
|
||||
d = {
|
||||
'd': 'key1',
|
||||
'd2': 'key2'
|
||||
}
|
||||
data = parse_dict_to_url(d)
|
||||
self.assertIsNotNone(data)
|
||||
|
||||
|
||||
+4
-1
@@ -31,6 +31,9 @@ sitemaps = {
|
||||
'static': StaticViewSitemap
|
||||
}
|
||||
|
||||
handler404 = 'blog.views.page_not_found_view'
|
||||
handler500 = 'blog.views.server_error_view'
|
||||
|
||||
urlpatterns = [
|
||||
url(r'^admin/', admin.site.urls),
|
||||
url(r'', include('blog.urls', namespace='blog', app_name='blog')),
|
||||
@@ -41,6 +44,6 @@ urlpatterns = [
|
||||
url(r'^sitemap\.xml$', sitemap, {'sitemaps': sitemaps},
|
||||
name='django.contrib.sitemaps.views.sitemap'),
|
||||
url(r'^feed/$', DjangoBlogFeed()),
|
||||
|
||||
url(r'^search', include('haystack.urls'), name='search'),
|
||||
url(r'', include('servermanager.urls', namespace='servermanager', app_name='servermanagers'))
|
||||
] + static(settings.STATIC_URL, document_root=settings.STATIC_ROOT)
|
||||
|
||||
+47
-6
@@ -13,18 +13,19 @@
|
||||
@time: 2017/1/19 上午2:30
|
||||
"""
|
||||
from django.core.cache import cache
|
||||
from django.contrib.sites.models import Site
|
||||
from hashlib import md5
|
||||
import mistune
|
||||
from mistune import escape, escape_link
|
||||
from pygments import highlight
|
||||
from pygments.lexers import get_lexer_by_name
|
||||
from pygments.formatters import html
|
||||
import logging
|
||||
|
||||
logger = logging.getLogger('djangoblog')
|
||||
from importlib import import_module
|
||||
import _thread
|
||||
from django.core.mail import EmailMultiAlternatives
|
||||
from django.conf import settings
|
||||
|
||||
SessionStore = import_module(settings.SESSION_ENGINE).SessionStore
|
||||
logger = logging.getLogger('djangoblog')
|
||||
|
||||
|
||||
def get_max_articleid_commentid():
|
||||
@@ -33,6 +34,11 @@ def get_max_articleid_commentid():
|
||||
return (Article.objects.latest().pk, Comment.objects.latest().pk)
|
||||
|
||||
|
||||
def get_md5(str):
|
||||
m = md5(str.encode('utf-8'))
|
||||
return m.hexdigest()
|
||||
|
||||
|
||||
def cache_decorator(expiration=3 * 60):
|
||||
def wrapper(func):
|
||||
def news(*args, **kwargs):
|
||||
@@ -101,17 +107,52 @@ def block_code(text, lang, inlinestyles=False, linenos=False):
|
||||
|
||||
|
||||
class BlogMarkDownRenderer(mistune.Renderer):
|
||||
def block_code(self, text, lang):
|
||||
def block_code(self, text, lang=None):
|
||||
# renderer has an options
|
||||
inlinestyles = self.options.get('inlinestyles')
|
||||
linenos = self.options.get('linenos')
|
||||
return block_code(text, lang, inlinestyles, linenos)
|
||||
|
||||
def autolink(self, link, is_email=False):
|
||||
text = link = escape(link)
|
||||
|
||||
class common_markdown():
|
||||
if is_email:
|
||||
link = 'mailto:%s' % link
|
||||
if not link:
|
||||
link = "#"
|
||||
site = Site.objects.get_current()
|
||||
nofollow = "" if link.find(site.domain) > 0 else "rel='nofollow'"
|
||||
return '<a href="%s" %s>%s</a>' % (link, nofollow, text)
|
||||
|
||||
def link(self, link, title, text):
|
||||
link = escape_link(link)
|
||||
site = Site.objects.get_current()
|
||||
nofollow = "" if link.find(site.domain) > 0 else "rel='nofollow'"
|
||||
if not link:
|
||||
link = "#"
|
||||
if not title:
|
||||
return '<a href="%s" %s>%s</a>' % (link, nofollow, text)
|
||||
title = escape(title, quote=True)
|
||||
return '<a href="%s" title="%s" %s>%s</a>' % (link, title, nofollow, text)
|
||||
|
||||
|
||||
class CommonMarkdown():
|
||||
@staticmethod
|
||||
def get_markdown(value):
|
||||
renderer = BlogMarkDownRenderer(inlinestyles=False)
|
||||
|
||||
mdp = mistune.Markdown(escape=True, renderer=renderer)
|
||||
return mdp(value)
|
||||
|
||||
|
||||
def send_email(emailto, title, content):
|
||||
msg = EmailMultiAlternatives(title, content, from_email=settings.DEFAULT_FROM_EMAIL, to=emailto)
|
||||
msg.content_subtype = "html"
|
||||
_thread.start_new_thread(msg.send, (msg,))
|
||||
|
||||
|
||||
def parse_dict_to_url(dict):
|
||||
from urllib.parse import quote
|
||||
url = '&'.join(['{}={}'.format(quote(k, safe='/'), quote(v, safe='/'))
|
||||
for k, v in dict.items()])
|
||||
return url
|
||||
|
||||
@@ -2,26 +2,55 @@
|
||||
|
||||
基于`python3.5`和`Django1.10`的博客。
|
||||
|
||||
[](https://travis-ci.org/liangliangyy/DjangoBlog) [](https://coveralls.io/github/liangliangyy/DjangoBlog?branch=master) [](https://requires.io/github/liangliangyy/DjangoBlog/requirements/?branch=master)
|
||||
[](https://travis-ci.org/liangliangyy/DjangoBlog) [](https://coveralls.io/github/liangliangyy/DjangoBlog?branch=master) [](https://requires.io/github/liangliangyy/DjangoBlog/requirements/?branch=master) []() []() []() []()
|
||||
|
||||
## 主要功能:
|
||||
- 文章,页面,分类目录,标签的添加,删除,编辑等。文章及页面支持`Markdown`,支持代码高亮。
|
||||
- 支持文章全文搜索。
|
||||
- 完整的评论功能,包括发表回复评论,以及评论的邮件提醒,支持`Markdown`。
|
||||
- 侧边栏功能,最新文章,最多阅读,标签云等。
|
||||
- 支持Oauth登陆,现已有Google,GitHub,facebook,微博登录。
|
||||
- 支持`Memcache`缓存,支持缓存自动刷新。
|
||||
- 简单的SEO功能,新建文章等会自动通知Google和百度。
|
||||
- 集成了简单的图床功能。
|
||||
- 集成`django-compressor`,自动压缩`css`,`js`。
|
||||
- 基于`python3`,支持`Django`多版本。`Django`的1.8,1.9,1.10,1.11均测试通过。
|
||||
- 网站异常邮件提醒,若有未捕捉到的异常会自动发送提醒邮件。
|
||||
- 集成了微信公众号功能,现在可以使用微信公众号来管理你的vps了。
|
||||
## 安装
|
||||
使用pip安装:
|
||||
`pip install -r requirements.txt`
|
||||
`pip install -Ur requirements.txt`
|
||||
|
||||
如果你没有pip,使用如下方式安装:
|
||||
OS X / Linux 电脑,终端下执行:
|
||||
|
||||
curl http://peak.telecommunity.com/dist/ez_setup.py | python
|
||||
curl https://raw.github.com/pypa/pip/master/contrib/get-pip.py | python
|
||||
|
||||
|
||||
windows电脑:
|
||||
下载 http://peak.telecommunity.com/dist/ez_setup.py 和 https://raw.github.com/pypa/pip/master/contrib/get-pip.py 这两个文件,双击运行。
|
||||
|
||||
|
||||
### 配置
|
||||
配置都是在`setting.py`中.
|
||||
|
||||
很多`setting`配置我都是写在环境变量里面的.并没有提交到`github`中来.例如`SECRET_KEY`,`OAHUTH`,`mysql`以及邮件部分的配置等.你可以直接修改代码成你自己的,或者在环境变量里面加入对应的配置就可以了.
|
||||
|
||||
`test`目录中的文件都是为了`travis`自动化测试使用的.不用去关注.或者直接使用.这样就可以集成`travis`自动化测试了.
|
||||
|
||||
`bin`目录是在`linux`环境中使用`Nginx`+`Gunicorn`+`virtualenv`+`supervisor`来部署的脚本和`Nginx`配置文件.可以参考我的文章:
|
||||
|
||||
>[使用Nginx+Gunicorn+virtualenv+supervisor来部署django项目](https://www.lylinux.org/%E4%BD%BF%E7%94%A8nginxgunicornvirtualenvsupervisor%E6%9D%A5%E9%83%A8%E7%BD%B2django%E9%A1%B9%E7%9B%AE.html)
|
||||
|
||||
有详细的部署介绍.
|
||||
|
||||
为了安全起见,没有把`SECRET_KEY`上传到Github中而是在环境变量中配置的,如果你想要正常运行的话,需要修改`settings.py`中的`SECRET_KEY`为你自己的就可以了。
|
||||
如:`SECRET_KEY = 'n9ceqv38)#&mwuat@(mjb_p%em$e8$qyr#fw9ot!=ba6lijx-6'`
|
||||
若本地部署后发现静态文件无法加载.请将`settings.py`中的`DEBUG=False`修改为`DEBUG=True`即可.
|
||||
|
||||
## 运行
|
||||
|
||||
|
||||
修改`DjangoBlog/setting.py` 修改数据库配置,如下所示:
|
||||
|
||||
|
||||
DATABASES = {
|
||||
'default': {
|
||||
'ENGINE': 'django.db.backends.mysql',
|
||||
@@ -32,16 +61,48 @@ windows电脑:
|
||||
'PORT': 3306,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
### 创建数据库
|
||||
|
||||
终端下执行:
|
||||
|
||||
|
||||
终端下执行:
|
||||
|
||||
./manage.py makemigrations
|
||||
./manage.py migrate
|
||||
./manage.py migrate
|
||||
### 创建超级用户
|
||||
|
||||
终端下执行:
|
||||
|
||||
./manage.py createsuperuser
|
||||
### 创建测试数据
|
||||
终端下执行:
|
||||
|
||||
./manage.py create_testdata
|
||||
### 收集静态文件
|
||||
终端下执行:
|
||||
|
||||
./manage.py collectstatic --noinput
|
||||
./manage.py compress --force
|
||||
### 开始运行:
|
||||
执行:
|
||||
执行:
|
||||
`./manage.py runserver`
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
浏览器打开: http://127.0.0.1:8000/ 就可以看到效果了。
|
||||
|
||||
|
||||
## 问题相关
|
||||
|
||||
有任何问题欢迎提Issue,或者将问题描述发送至我邮箱 `liangliangyy#gmail.com`.我会尽快解答.推荐提交Issue方式.
|
||||
## 捐赠
|
||||
如果您觉得本项目对您有所帮助,欢迎您请我喝杯咖啡,您的支持是我最大的动力,您可以扫描下方二维码为我付款,谢谢。
|
||||
### 支付宝:
|
||||
<div align="center">
|
||||
<img src="https://resource.lylinux.net/image/2017/12/16/IMG_0207.jpg" width="300" height="300" />
|
||||
</div>
|
||||
|
||||
### 微信:
|
||||
<div align="center">
|
||||
<img src="https://resource.lylinux.net/image/2017/12/16/IMG_0206.jpg" width="300" height="300" />
|
||||
</div>
|
||||
|
||||
+9
-1
@@ -3,4 +3,12 @@ from django.contrib import admin
|
||||
# Register your models here.
|
||||
from .models import BlogUser
|
||||
|
||||
admin.site.register(BlogUser)
|
||||
|
||||
|
||||
class BlogUserAdmin(admin.ModelAdmin):
|
||||
list_display = ('id', 'nickname', 'username','email','last_login','date_joined')
|
||||
list_display_links = ('id', 'username')
|
||||
|
||||
|
||||
|
||||
admin.site.register(BlogUser, BlogUserAdmin)
|
||||
|
||||
+6
-3
@@ -22,7 +22,8 @@ class LoginForm(AuthenticationForm):
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(LoginForm, self).__init__(*args, **kwargs)
|
||||
self.fields['username'].widget = widgets.TextInput(attrs={'placeholder': "username", "class": "form-control"})
|
||||
self.fields['password'].widget = widgets.PasswordInput(attrs={'placeholder': "password", "class": "form-control"})
|
||||
self.fields['password'].widget = widgets.PasswordInput(
|
||||
attrs={'placeholder': "password", "class": "form-control"})
|
||||
|
||||
|
||||
class RegisterForm(UserCreationForm):
|
||||
@@ -31,8 +32,10 @@ class RegisterForm(UserCreationForm):
|
||||
|
||||
self.fields['username'].widget = widgets.TextInput(attrs={'placeholder': "username", "class": "form-control"})
|
||||
self.fields['email'].widget = widgets.EmailInput(attrs={'placeholder': "email", "class": "form-control"})
|
||||
self.fields['password1'].widget = widgets.PasswordInput(attrs={'placeholder': "password", "class": "form-control"})
|
||||
self.fields['password2'].widget = widgets.PasswordInput(attrs={'placeholder': "repeat password", "class": "form-control"})
|
||||
self.fields['password1'].widget = widgets.PasswordInput(
|
||||
attrs={'placeholder': "password", "class": "form-control"})
|
||||
self.fields['password2'].widget = widgets.PasswordInput(
|
||||
attrs={'placeholder': "repeat password", "class": "form-control"})
|
||||
|
||||
class Meta:
|
||||
model = get_user_model()
|
||||
|
||||
+4
-12
@@ -2,24 +2,16 @@ from django.db import models
|
||||
from django.contrib.auth.models import AbstractUser, BaseUserManager
|
||||
from django.core.urlresolvers import reverse
|
||||
from django.contrib.sites.models import Site
|
||||
from django.utils.timezone import now
|
||||
|
||||
|
||||
# Create your models here.
|
||||
|
||||
"""
|
||||
class BlogUserManager(BaseUserManager):
|
||||
def create_user(self, email, username, password=None):
|
||||
user = self.model(
|
||||
username=username, email=email, nickname=username
|
||||
)
|
||||
user.set_password(password)
|
||||
user.save(using=self._db)
|
||||
return user
|
||||
"""
|
||||
|
||||
|
||||
class BlogUser(AbstractUser):
|
||||
nickname = models.CharField('昵称', max_length=50, blank=True)
|
||||
mugshot = models.ImageField('头像', upload_to='upload/mugshots', blank=True)
|
||||
created_time = models.DateTimeField('创建时间', default=now)
|
||||
last_mod_time = models.DateTimeField('修改时间', default=now)
|
||||
|
||||
# objects = BlogUserManager()
|
||||
|
||||
|
||||
+82
-1
@@ -1,3 +1,84 @@
|
||||
from django.test import TestCase
|
||||
from django.test import Client, RequestFactory, TestCase
|
||||
from blog.models import Article, Category, Tag
|
||||
from django.contrib.auth import get_user_model
|
||||
from django.contrib.sites.models import Site
|
||||
import datetime
|
||||
from accounts.models import BlogUser
|
||||
from django.core.urlresolvers import reverse
|
||||
|
||||
|
||||
# Create your tests here.
|
||||
|
||||
class AccountTest(TestCase):
|
||||
def setUp(self):
|
||||
self.client = Client()
|
||||
self.factory = RequestFactory()
|
||||
|
||||
def test_validate_account(self):
|
||||
site = Site.objects.get_current().domain
|
||||
user = BlogUser.objects.create_superuser(email="liangliangyy1@gmail.com",
|
||||
username="liangliangyy1", password="liangliangyy1")
|
||||
|
||||
self.client.login(username='liangliangyy1', password='liangliangyy1')
|
||||
response = self.client.get('/admin/')
|
||||
self.assertEqual(response.status_code, 200)
|
||||
|
||||
category = Category()
|
||||
category.name = "categoryaaa"
|
||||
category.created_time = datetime.datetime.now()
|
||||
category.last_mod_time = datetime.datetime.now()
|
||||
category.save()
|
||||
|
||||
article = Article()
|
||||
article.title = "nicetitleaaa"
|
||||
article.body = "nicecontentaaa"
|
||||
article.author = user
|
||||
article.category = category
|
||||
article.type = 'a'
|
||||
article.status = 'p'
|
||||
article.save()
|
||||
|
||||
response = self.client.get(article.get_admin_url())
|
||||
self.assertEqual(response.status_code, 200)
|
||||
|
||||
def test_validate_register(self):
|
||||
self.assertEquals(0, len(BlogUser.objects.filter(email='user123@user.com')))
|
||||
response = self.client.post(reverse('account:register'), {
|
||||
'username': 'user1233',
|
||||
'email': 'user123@user.com',
|
||||
'password1': 'password123',
|
||||
'password2': 'password123',
|
||||
})
|
||||
self.assertEquals(1, len(BlogUser.objects.filter(email='user123@user.com')))
|
||||
|
||||
self.client.login(username='user1233', password='password123')
|
||||
user = BlogUser.objects.filter(email='user123@user.com')[0]
|
||||
user.is_superuser = True
|
||||
user.is_staff = True
|
||||
user.save()
|
||||
article = Article()
|
||||
article.title = "nicetitle333"
|
||||
article.body = "nicecontentttt"
|
||||
article.author = user
|
||||
|
||||
article.type = 'a'
|
||||
article.status = 'p'
|
||||
article.save()
|
||||
|
||||
response = self.client.get(article.get_admin_url())
|
||||
self.assertEqual(response.status_code, 200)
|
||||
|
||||
response = self.client.get(reverse('account:logout'))
|
||||
self.assertIn(response.status_code, [301, 302])
|
||||
|
||||
response = self.client.get(article.get_admin_url())
|
||||
self.assertIn(response.status_code, [301, 302])
|
||||
|
||||
response = self.client.post(reverse('account:login'), {
|
||||
'username': 'user1233',
|
||||
'password': 'password123'
|
||||
})
|
||||
self.assertIn(response.status_code, [301, 302])
|
||||
|
||||
response = self.client.get(article.get_admin_url())
|
||||
self.assertEqual(response.status_code, 200)
|
||||
|
||||
+15
-4
@@ -58,23 +58,34 @@ class LoginView(FormView):
|
||||
|
||||
return super(LoginView, self).dispatch(request, *args, **kwargs)
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
redirect_to = self.request.GET.get(self.redirect_field_name)
|
||||
if redirect_to is None:
|
||||
redirect_to = '/'
|
||||
kwargs['redirect_to'] = redirect_to
|
||||
|
||||
return super(LoginView, self).get_context_data(**kwargs)
|
||||
|
||||
def form_valid(self, form):
|
||||
form = AuthenticationForm(data=self.request.POST, request=self.request)
|
||||
|
||||
if form.is_valid():
|
||||
from DjangoBlog.utils import cache
|
||||
if cache != None:
|
||||
if cache and cache is not None:
|
||||
cache.clear()
|
||||
print(self.redirect_field_name)
|
||||
redirect_to = self.request.GET.get(self.redirect_field_name)
|
||||
auth.login(self.request, form.get_user())
|
||||
|
||||
return HttpResponseRedirect('/')
|
||||
return super(LoginView, self).form_valid(form)
|
||||
# return HttpResponseRedirect('/')
|
||||
else:
|
||||
return self.render_to_response({
|
||||
'form': form
|
||||
})
|
||||
|
||||
def get_success_url(self):
|
||||
redirect_to = self.request.GET.get(self.redirectfieldname)
|
||||
print(self.redirect_field_name)
|
||||
redirect_to = self.request.POST.get(self.redirect_field_name)
|
||||
if not is_safe_url(url=redirect_to, host=self.request.get_host()):
|
||||
redirect_to = self.success_url
|
||||
return redirect_to
|
||||
|
||||
+2
-2
@@ -3,8 +3,8 @@
|
||||
NAME="djangoblog" # Name of the application
|
||||
DJANGODIR=//var/www/DjangoBlog # Django project directory
|
||||
SOCKFILE=/var/www/DjangoBlog/run/gunicorn.sock # we will communicte using this unix socket
|
||||
USER=www-data # the user to run as
|
||||
GROUP=www-data # the group to run as
|
||||
USER=root # the user to run as
|
||||
GROUP=root # the group to run as
|
||||
NUM_WORKERS=3 # how many worker processes should Gunicorn spawn
|
||||
DJANGO_SETTINGS_MODULE=DjangoBlog.settings # which settings file should Django use
|
||||
DJANGO_WSGI_MODULE=DjangoBlog.wsgi # WSGI module name
|
||||
|
||||
+51
-5
@@ -1,9 +1,28 @@
|
||||
from django.contrib import admin
|
||||
|
||||
# Register your models here.
|
||||
from .models import Article, Category, Tag, Links
|
||||
from .models import Article, Category, Tag, Links, SideBar
|
||||
from pagedown.widgets import AdminPagedownWidget
|
||||
from django import forms
|
||||
from django.contrib.auth import get_user_model
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
|
||||
class ArticleListFilter(admin.SimpleListFilter):
|
||||
title = _("作者")
|
||||
parameter_name = 'author'
|
||||
|
||||
def lookups(self, request, model_admin):
|
||||
authors = list(set(map(lambda x: x.author, Article.objects.all())))
|
||||
for author in authors:
|
||||
yield (author.id, _(author.username))
|
||||
|
||||
def queryset(self, request, queryset):
|
||||
id = self.value()
|
||||
if id:
|
||||
return queryset.filter(author__id__exact=id)
|
||||
else:
|
||||
return queryset
|
||||
|
||||
|
||||
class ArticleForm(forms.ModelForm):
|
||||
@@ -16,6 +35,16 @@ class ArticleForm(forms.ModelForm):
|
||||
|
||||
class ArticlelAdmin(admin.ModelAdmin):
|
||||
form = ArticleForm
|
||||
list_display = ('id', 'title', 'author', 'created_time', 'views', 'status', 'type')
|
||||
list_display_links = ('id', 'title')
|
||||
list_filter = (ArticleListFilter, 'status', 'type', 'category', 'tags')
|
||||
filter_horizontal = ('tags',)
|
||||
exclude = ('slug', 'created_time', 'last_mod_time')
|
||||
|
||||
def get_form(self, request, obj=None, **kwargs):
|
||||
form = super(ArticlelAdmin, self).get_form(request, obj, **kwargs)
|
||||
form.base_fields['author'].queryset = get_user_model().objects.filter(is_superuser=True)
|
||||
return form
|
||||
|
||||
def save_model(self, request, obj, form, change):
|
||||
super(ArticlelAdmin, self).save_model(request, obj, form, change)
|
||||
@@ -23,8 +52,25 @@ class ArticlelAdmin(admin.ModelAdmin):
|
||||
cache.clear()
|
||||
|
||||
|
||||
class TagAdmin(admin.ModelAdmin):
|
||||
exclude = ('slug', 'last_mod_time', 'created_time')
|
||||
|
||||
|
||||
class CategoryAdmin(admin.ModelAdmin):
|
||||
exclude = ('slug', 'last_mod_time', 'created_time')
|
||||
|
||||
|
||||
class LinksAdmin(admin.ModelAdmin):
|
||||
exclude = ('last_mod_time', 'created_time')
|
||||
|
||||
|
||||
class SideBarAdmin(admin.ModelAdmin):
|
||||
list_display = ('name', 'content', 'is_enable', 'sequence')
|
||||
exclude = ('last_mod_time', 'created_time')
|
||||
|
||||
|
||||
admin.site.register(Article, ArticlelAdmin)
|
||||
# admin.site.register(BlogPage, ArticlelAdmin)
|
||||
admin.site.register(Category)
|
||||
admin.site.register(Tag)
|
||||
admin.site.register(Links)
|
||||
admin.site.register(Category, CategoryAdmin)
|
||||
admin.site.register(Tag, TagAdmin)
|
||||
admin.site.register(Links, LinksAdmin)
|
||||
admin.site.register(SideBar, SideBarAdmin)
|
||||
|
||||
@@ -0,0 +1,54 @@
|
||||
#!/usr/bin/env python
|
||||
# encoding: utf-8
|
||||
|
||||
|
||||
"""
|
||||
@version: ??
|
||||
@author: liangliangyy
|
||||
@license: MIT Licence
|
||||
@contact: liangliangyy@gmail.com
|
||||
@site: https://www.lylinux.org/
|
||||
@software: PyCharm
|
||||
@file: create_testdata.py
|
||||
@time: 2017/3/11 上午1:58
|
||||
"""
|
||||
|
||||
from django.core.management.base import BaseCommand
|
||||
from blog.models import Article, Tag, Category
|
||||
from django.contrib.auth import get_user_model
|
||||
from django.core.exceptions import ObjectDoesNotExist
|
||||
import datetime
|
||||
|
||||
|
||||
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='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'))
|
||||
@@ -15,7 +15,7 @@
|
||||
|
||||
from django.core.management.base import BaseCommand, CommandError
|
||||
from blog.models import Article, Tag, Category
|
||||
from DjangoBlog.spider_notify import spider_notify
|
||||
from DjangoBlog.spider_notify import SpiderNotify
|
||||
from django.contrib.sites.models import Site
|
||||
|
||||
site = Site.objects.get_current().domain
|
||||
@@ -50,5 +50,5 @@ class Command(BaseCommand):
|
||||
urls.append(self.get_full_url(url))
|
||||
|
||||
self.stdout.write(self.style.SUCCESS('start notify %d urls' % len(urls)))
|
||||
spider_notify.baidu_notify(urls)
|
||||
SpiderNotify.baidu_notify(urls)
|
||||
self.stdout.write(self.style.SUCCESS('finish notify'))
|
||||
|
||||
+82
-108
@@ -2,30 +2,28 @@ from django.db import models
|
||||
from django.core.urlresolvers import reverse
|
||||
from django.conf import settings
|
||||
from uuslug import slugify
|
||||
from DjangoBlog.spider_notify import spider_notify
|
||||
|
||||
from django.contrib.sites.models import Site
|
||||
from DjangoBlog.utils import cache_decorator, logger
|
||||
from DjangoBlog.utils import cache_decorator, logger, cache
|
||||
from django.utils.functional import cached_property
|
||||
from django.utils.timezone import now
|
||||
|
||||
|
||||
class BaseModel(models.Model):
|
||||
slug = models.SlugField(default='no-slug', max_length=60, blank=True)
|
||||
created_time = models.DateTimeField('创建时间', default=now)
|
||||
last_mod_time = models.DateTimeField('修改时间', default=now)
|
||||
|
||||
def save(self, *args, **kwargs):
|
||||
from DjangoBlog.blog_signals import article_save_signal
|
||||
if not self.slug or self.slug == 'no-slug' or not self.id:
|
||||
# Only set the slug when the object is created.
|
||||
slug = self.title if 'title' in self.__dict__ else self.name
|
||||
self.slug = slugify(slug)
|
||||
super().save(*args, **kwargs)
|
||||
|
||||
if 'update_fields' in kwargs and len(kwargs['update_fields']) == 1 and kwargs['update_fields'][0] == 'views':
|
||||
return
|
||||
try:
|
||||
notify_url = self.get_full_url()
|
||||
spider_notify.baidu_notify([notify_url])
|
||||
except Exception as ex:
|
||||
logger.error("notify sipder", ex)
|
||||
print(ex)
|
||||
# type = self.__class__.__name__
|
||||
is_update_views = 'update_fields' in kwargs and len(kwargs['update_fields']) == 1 and kwargs['update_fields'][
|
||||
0] == 'views'
|
||||
article_save_signal.send(sender=self.__class__, is_update_views=is_update_views, id=self.id)
|
||||
|
||||
def get_full_url(self):
|
||||
site = Site.objects.get_current().domain
|
||||
@@ -52,8 +50,6 @@ class Article(BaseModel):
|
||||
)
|
||||
title = models.CharField('标题', max_length=200, unique=True)
|
||||
body = models.TextField('正文')
|
||||
created_time = models.DateTimeField('创建时间', auto_now_add=True)
|
||||
last_mod_time = models.DateTimeField('修改时间', auto_now=True)
|
||||
pub_time = models.DateTimeField('发布时间', blank=True, null=True)
|
||||
status = models.CharField('文章状态', max_length=1, choices=STATUS_CHOICES, default='p')
|
||||
comment_status = models.CharField('评论状态', max_length=1, choices=COMMENT_STATUS, default='o')
|
||||
@@ -74,51 +70,42 @@ class Article(BaseModel):
|
||||
get_latest_by = 'created_time'
|
||||
|
||||
def get_absolute_url(self):
|
||||
|
||||
return reverse('blog:detail', kwargs=
|
||||
{
|
||||
return reverse('blog:detailbyid', kwargs={
|
||||
'article_id': self.id,
|
||||
'year': self.created_time.year,
|
||||
'month': self.created_time.month,
|
||||
'day': self.created_time.day,
|
||||
'slug': self.slug
|
||||
'day': self.created_time.day
|
||||
})
|
||||
|
||||
@cache_decorator(60 * 60 * 10)
|
||||
def get_category_tree(self):
|
||||
names = []
|
||||
tree = self.category.get_category_tree()
|
||||
names = list(map(lambda c: (c.name, c.get_absolute_url()), tree))
|
||||
|
||||
def parse(category):
|
||||
names.append((category.name, category.get_absolute_url()))
|
||||
if category.parent_category:
|
||||
parse(category.parent_category)
|
||||
|
||||
parse(self.category)
|
||||
return names
|
||||
|
||||
def save(self, *args, **kwargs):
|
||||
# self.summary = self.summary or self.body[:settings.ARTICLE_SUB_LENGTH]
|
||||
if not self.slug or self.slug == 'no-slug' or not self.id:
|
||||
# Only set the slug when the object is created.
|
||||
self.slug = slugify(self.title)
|
||||
"""
|
||||
try:
|
||||
notify = sipder_notify()
|
||||
notify.notify(self.get_full_url())
|
||||
except Exception as e:
|
||||
print(e)
|
||||
"""
|
||||
|
||||
super().save(*args, **kwargs)
|
||||
|
||||
def viewed(self):
|
||||
self.views += 1
|
||||
self.save(update_fields=['views'])
|
||||
|
||||
"""
|
||||
def comment_list(self):
|
||||
comments = self.comment_set.all()
|
||||
parent_comments = comments.filter(parent_comment=None)
|
||||
"""
|
||||
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.all()
|
||||
cache.set(cache_key, comments)
|
||||
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)
|
||||
@@ -135,75 +122,9 @@ class Article(BaseModel):
|
||||
return Article.objects.filter(id__lt=self.id, status='p').first()
|
||||
|
||||
|
||||
'''
|
||||
class BlogPage(models.Model):
|
||||
"""文章"""
|
||||
STATUS_CHOICES = (
|
||||
('d', '草稿'),
|
||||
('p', '发表'),
|
||||
)
|
||||
COMMENT_STATUS = (
|
||||
('o', '打开'),
|
||||
('c', '关闭'),
|
||||
)
|
||||
title = models.CharField('标题', max_length=200)
|
||||
body = models.TextField('正文')
|
||||
created_time = models.DateTimeField('创建时间', auto_now_add=True)
|
||||
last_mod_time = models.DateTimeField('修改时间', auto_now=True)
|
||||
pub_time = models.DateTimeField('发布时间', blank=True, null=True,
|
||||
help_text="不指定发布时间则视为草稿,可以指定未来时间,到时将自动发布。")
|
||||
status = models.CharField('文章状态', max_length=1, choices=STATUS_CHOICES, default='o')
|
||||
comment_status = models.CharField('评论状态', max_length=1, choices=COMMENT_STATUS)
|
||||
# summary = models.CharField('摘要', max_length=200, blank=True, help_text="可选,若为空将摘取正文的前300个字符。")
|
||||
views = models.PositiveIntegerField('浏览量', default=0)
|
||||
|
||||
author = models.ForeignKey(settings.AUTH_USER_MODEL, verbose_name='作者', on_delete=models.CASCADE)
|
||||
slug = models.SlugField(default='no-slug', max_length=60, blank=True)
|
||||
|
||||
class Meta:
|
||||
ordering = ['-pub_time']
|
||||
verbose_name = "页面"
|
||||
verbose_name_plural = verbose_name
|
||||
|
||||
def __str__(self):
|
||||
return self.title
|
||||
|
||||
def get_absolute_url(self):
|
||||
return reverse('blog:pagedetail', kwargs=
|
||||
{
|
||||
'page_id': self.id,
|
||||
'year': self.created_time.year,
|
||||
'month': self.created_time.month,
|
||||
'day': self.created_time.day,
|
||||
'slug': self.slug
|
||||
})
|
||||
|
||||
def save(self, *args, **kwargs):
|
||||
# self.summary = self.summary or self.body[:settings.ARTICLE_SUB_LENGTH]
|
||||
if not self.slug or self.slug == 'no-slug' or not self.id:
|
||||
# Only set the slug when the object is created.
|
||||
self.slug = slugify(self.title)
|
||||
|
||||
super().save(*args, **kwargs)
|
||||
|
||||
def viewed(self):
|
||||
self.views += 1
|
||||
self.save(update_fields=['views'])
|
||||
|
||||
def comment_list(self):
|
||||
comments = self.comment_set.all()
|
||||
parent_comments = comments.filter(parent_comment=None)
|
||||
|
||||
def get_category_tree(self):
|
||||
return []
|
||||
'''
|
||||
|
||||
|
||||
class Category(BaseModel):
|
||||
"""文章分类"""
|
||||
name = models.CharField('分类名', max_length=30, unique=True)
|
||||
created_time = models.DateTimeField('创建时间', auto_now_add=True)
|
||||
last_mod_time = models.DateTimeField('修改时间', auto_now=True)
|
||||
parent_category = models.ForeignKey('self', verbose_name="父级分类", blank=True, null=True)
|
||||
|
||||
class Meta:
|
||||
@@ -217,12 +138,47 @@ class Category(BaseModel):
|
||||
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('标签名', max_length=30, unique=True)
|
||||
created_time = models.DateTimeField('创建时间', auto_now_add=True)
|
||||
last_mod_time = models.DateTimeField('修改时间', auto_now=True)
|
||||
|
||||
def __str__(self):
|
||||
return self.name
|
||||
@@ -245,8 +201,8 @@ class Links(models.Model):
|
||||
name = models.CharField('链接名称', max_length=30, unique=True)
|
||||
link = models.URLField('链接地址')
|
||||
sequence = models.IntegerField('排序', unique=True)
|
||||
created_time = models.DateTimeField('创建时间', auto_now_add=True)
|
||||
last_mod_time = models.DateTimeField('修改时间', auto_now=True)
|
||||
created_time = models.DateTimeField('创建时间', default=now)
|
||||
last_mod_time = models.DateTimeField('修改时间', default=now)
|
||||
|
||||
class Meta:
|
||||
ordering = ['sequence']
|
||||
@@ -255,3 +211,21 @@ class Links(models.Model):
|
||||
|
||||
def __str__(self):
|
||||
return self.name
|
||||
|
||||
|
||||
class SideBar(models.Model):
|
||||
"""侧边栏,可以展示一些html内容"""
|
||||
name = models.CharField('标题', max_length=100)
|
||||
content = models.TextField("内容")
|
||||
sequence = models.IntegerField('排序', unique=True)
|
||||
is_enable = models.BooleanField('是否启用', default=True)
|
||||
created_time = models.DateTimeField('创建时间', default=now)
|
||||
last_mod_time = models.DateTimeField('修改时间', default=now)
|
||||
|
||||
class Meta:
|
||||
ordering = ['sequence']
|
||||
verbose_name = '侧边栏'
|
||||
verbose_name_plural = verbose_name
|
||||
|
||||
def __str__(self):
|
||||
return self.name
|
||||
|
||||
@@ -21,7 +21,6 @@ class ArticleIndex(indexes.SearchIndex, indexes.Indexable):
|
||||
# title = indexes.CharField(document=True, use_template=True)
|
||||
text = indexes.CharField(document=True, use_template=True)
|
||||
|
||||
|
||||
def get_model(self):
|
||||
return Article
|
||||
|
||||
|
||||
@@ -0,0 +1,74 @@
|
||||
/* Make clicks pass-through */
|
||||
#nprogress {
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
#nprogress .bar {
|
||||
background: red;
|
||||
|
||||
position: fixed;
|
||||
z-index: 1031;
|
||||
top: 0;
|
||||
left: 0;
|
||||
|
||||
width: 100%;
|
||||
height: 2px;
|
||||
}
|
||||
|
||||
/* Fancy blur effect */
|
||||
#nprogress .peg {
|
||||
display: block;
|
||||
position: absolute;
|
||||
right: 0px;
|
||||
width: 100px;
|
||||
height: 100%;
|
||||
box-shadow: 0 0 10px #29d, 0 0 5px #29d;
|
||||
opacity: 1.0;
|
||||
|
||||
-webkit-transform: rotate(3deg) translate(0px, -4px);
|
||||
-ms-transform: rotate(3deg) translate(0px, -4px);
|
||||
transform: rotate(3deg) translate(0px, -4px);
|
||||
}
|
||||
|
||||
/* Remove these to get rid of the spinner */
|
||||
#nprogress .spinner {
|
||||
display: block;
|
||||
position: fixed;
|
||||
z-index: 1031;
|
||||
top: 15px;
|
||||
right: 15px;
|
||||
}
|
||||
|
||||
#nprogress .spinner-icon {
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
box-sizing: border-box;
|
||||
|
||||
border: solid 2px transparent;
|
||||
border-top-color: red;
|
||||
border-left-color: red;
|
||||
border-radius: 50%;
|
||||
|
||||
-webkit-animation: nprogress-spinner 400ms linear infinite;
|
||||
animation: nprogress-spinner 400ms linear infinite;
|
||||
}
|
||||
|
||||
.nprogress-custom-parent {
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.nprogress-custom-parent #nprogress .spinner,
|
||||
.nprogress-custom-parent #nprogress .bar {
|
||||
position: absolute;
|
||||
}
|
||||
|
||||
@-webkit-keyframes nprogress-spinner {
|
||||
0% { -webkit-transform: rotate(0deg); }
|
||||
100% { -webkit-transform: rotate(360deg); }
|
||||
}
|
||||
@keyframes nprogress-spinner {
|
||||
0% { transform: rotate(0deg); }
|
||||
100% { transform: rotate(360deg); }
|
||||
}
|
||||
|
||||
@@ -0,0 +1,305 @@
|
||||
|
||||
.icon-sn-google {
|
||||
background-position: 0 -28px;
|
||||
}
|
||||
|
||||
.icon-sn-bg-google {
|
||||
background-color: #4285f4;
|
||||
background-position: 0 0;
|
||||
}
|
||||
|
||||
.fa-sn-google {
|
||||
color: #4285f4;
|
||||
}
|
||||
|
||||
.icon-sn-github {
|
||||
background-position: -28px -28px;
|
||||
}
|
||||
|
||||
.icon-sn-bg-github {
|
||||
background-color: #333;
|
||||
background-position: -28px 0;
|
||||
}
|
||||
|
||||
.fa-sn-github {
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.icon-sn-weibo {
|
||||
background-position: -56px -28px;
|
||||
}
|
||||
|
||||
.icon-sn-bg-weibo {
|
||||
background-color: #e90d24;
|
||||
background-position: -56px 0;
|
||||
}
|
||||
|
||||
.fa-sn-weibo {
|
||||
color: #e90d24;
|
||||
}
|
||||
|
||||
.icon-sn-qq {
|
||||
background-position: -84px -28px;
|
||||
}
|
||||
|
||||
.icon-sn-bg-qq {
|
||||
background-color: #0098e6;
|
||||
background-position: -84px 0;
|
||||
}
|
||||
|
||||
.fa-sn-qq {
|
||||
color: #0098e6;
|
||||
}
|
||||
|
||||
.icon-sn-twitter {
|
||||
background-position: -112px -28px;
|
||||
}
|
||||
|
||||
.icon-sn-bg-twitter {
|
||||
background-color: #50abf1;
|
||||
background-position: -112px 0;
|
||||
}
|
||||
|
||||
.fa-sn-twitter {
|
||||
color: #50abf1;
|
||||
}
|
||||
|
||||
.icon-sn-facebook {
|
||||
background-position: -140px -28px;
|
||||
}
|
||||
|
||||
.icon-sn-bg-facebook {
|
||||
background-color: #4862a3;
|
||||
background-position: -140px 0;
|
||||
}
|
||||
|
||||
.fa-sn-facebook {
|
||||
color: #4862a3;
|
||||
}
|
||||
|
||||
.icon-sn-renren {
|
||||
background-position: -168px -28px;
|
||||
}
|
||||
|
||||
.icon-sn-bg-renren {
|
||||
background-color: #197bc8;
|
||||
background-position: -168px 0;
|
||||
}
|
||||
|
||||
.fa-sn-renren {
|
||||
color: #197bc8;
|
||||
}
|
||||
|
||||
.icon-sn-tqq {
|
||||
background-position: -196px -28px;
|
||||
}
|
||||
|
||||
.icon-sn-bg-tqq {
|
||||
background-color: #1f9ed2;
|
||||
background-position: -196px 0;
|
||||
}
|
||||
|
||||
.fa-sn-tqq {
|
||||
color: #1f9ed2;
|
||||
}
|
||||
|
||||
.icon-sn-douban {
|
||||
background-position: -224px -28px;
|
||||
}
|
||||
|
||||
.icon-sn-bg-douban {
|
||||
background-color: #279738;
|
||||
background-position: -224px 0;
|
||||
}
|
||||
|
||||
.fa-sn-douban {
|
||||
color: #279738;
|
||||
}
|
||||
|
||||
.icon-sn-weixin {
|
||||
background-position: -252px -28px;
|
||||
}
|
||||
|
||||
.icon-sn-bg-weixin {
|
||||
background-color: #00b500;
|
||||
background-position: -252px 0;
|
||||
}
|
||||
|
||||
.fa-sn-weixin {
|
||||
color: #00b500;
|
||||
}
|
||||
|
||||
.icon-sn-dotted {
|
||||
background-position: -280px -28px;
|
||||
}
|
||||
|
||||
.icon-sn-bg-dotted {
|
||||
background-color: #eee;
|
||||
background-position: -280px 0;
|
||||
}
|
||||
|
||||
.fa-sn-dotted {
|
||||
color: #eee;
|
||||
}
|
||||
|
||||
.icon-sn-site {
|
||||
background-position: -308px -28px;
|
||||
}
|
||||
|
||||
.icon-sn-bg-site {
|
||||
background-color: #00b500;
|
||||
background-position: -308px 0;
|
||||
}
|
||||
|
||||
.fa-sn-site {
|
||||
color: #00b500;
|
||||
}
|
||||
|
||||
.icon-sn-linkedin {
|
||||
background-position: -336px -28px;
|
||||
}
|
||||
|
||||
.icon-sn-bg-linkedin {
|
||||
background-color: #0077b9;
|
||||
background-position: -336px 0;
|
||||
}
|
||||
|
||||
.fa-sn-linkedin {
|
||||
color: #0077b9;
|
||||
}
|
||||
|
||||
[class*=icon-sn-] {
|
||||
display: inline-block;
|
||||
background-image: url('../img/icon-sn.svg');
|
||||
background-repeat: no-repeat;
|
||||
width: 28px;
|
||||
height: 28px;
|
||||
vertical-align: middle;
|
||||
background-size: auto 56px;
|
||||
}
|
||||
|
||||
[class*=icon-sn-]:hover {
|
||||
opacity: .8;
|
||||
filter: alpha(opacity=80);
|
||||
}
|
||||
|
||||
.btn-sn-google {
|
||||
background: #4285f4;
|
||||
}
|
||||
|
||||
.btn-sn-google:active, .btn-sn-google:focus, .btn-sn-google:hover {
|
||||
background: #2a75f3;
|
||||
}
|
||||
|
||||
.btn-sn-github {
|
||||
background: #333;
|
||||
}
|
||||
|
||||
.btn-sn-github:active, .btn-sn-github:focus, .btn-sn-github:hover {
|
||||
background: #262626;
|
||||
}
|
||||
|
||||
.btn-sn-weibo {
|
||||
background: #e90d24;
|
||||
}
|
||||
|
||||
.btn-sn-weibo:active, .btn-sn-weibo:focus, .btn-sn-weibo:hover {
|
||||
background: #d10c20;
|
||||
}
|
||||
|
||||
.btn-sn-qq {
|
||||
background: #0098e6;
|
||||
}
|
||||
|
||||
.btn-sn-qq:active, .btn-sn-qq:focus, .btn-sn-qq:hover {
|
||||
background: #0087cd;
|
||||
}
|
||||
|
||||
.btn-sn-twitter {
|
||||
background: #50abf1;
|
||||
}
|
||||
|
||||
.btn-sn-twitter:active, .btn-sn-twitter:focus, .btn-sn-twitter:hover {
|
||||
background: #38a0ef;
|
||||
}
|
||||
|
||||
.btn-sn-facebook {
|
||||
background: #4862a3;
|
||||
}
|
||||
|
||||
.btn-sn-facebook:active, .btn-sn-facebook:focus, .btn-sn-facebook:hover {
|
||||
background: #405791;
|
||||
}
|
||||
|
||||
.btn-sn-renren {
|
||||
background: #197bc8;
|
||||
}
|
||||
|
||||
.btn-sn-renren:active, .btn-sn-renren:focus, .btn-sn-renren:hover {
|
||||
background: #166db1;
|
||||
}
|
||||
|
||||
.btn-sn-tqq {
|
||||
background: #1f9ed2;
|
||||
}
|
||||
|
||||
.btn-sn-tqq:active, .btn-sn-tqq:focus, .btn-sn-tqq:hover {
|
||||
background: #1c8dbc;
|
||||
}
|
||||
|
||||
.btn-sn-douban {
|
||||
background: #279738;
|
||||
}
|
||||
|
||||
.btn-sn-douban:active, .btn-sn-douban:focus, .btn-sn-douban:hover {
|
||||
background: #228330;
|
||||
}
|
||||
|
||||
.btn-sn-weixin {
|
||||
background: #00b500;
|
||||
}
|
||||
|
||||
.btn-sn-weixin:active, .btn-sn-weixin:focus, .btn-sn-weixin:hover {
|
||||
background: #009c00;
|
||||
}
|
||||
|
||||
.btn-sn-dotted {
|
||||
background: #eee;
|
||||
}
|
||||
|
||||
.btn-sn-dotted:active, .btn-sn-dotted:focus, .btn-sn-dotted:hover {
|
||||
background: #e1e1e1;
|
||||
}
|
||||
|
||||
.btn-sn-site {
|
||||
background: #00b500;
|
||||
}
|
||||
|
||||
.btn-sn-site:active, .btn-sn-site:focus, .btn-sn-site:hover {
|
||||
background: #009c00;
|
||||
}
|
||||
|
||||
.btn-sn-linkedin {
|
||||
background: #0077b9;
|
||||
}
|
||||
|
||||
.btn-sn-linkedin:active, .btn-sn-linkedin:focus, .btn-sn-linkedin:hover {
|
||||
background: #0067a0;
|
||||
}
|
||||
|
||||
[class*=btn-sn-], [class*=btn-sn-]:active, [class*=btn-sn-]:focus, [class*=btn-sn-]:hover {
|
||||
border: none;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.btn-sn-more {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.btn-sn-more, .btn-sn-more:active, .btn-sn-more:hover {
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
[class*=btn-sn-] [class*=icon-sn-] {
|
||||
background-color: transparent;
|
||||
}
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -17,3 +17,55 @@ function cancel_reply() {
|
||||
$("#id_parent_comment_id").val('')
|
||||
$("#commentform").appendTo($("#respond"));
|
||||
}
|
||||
|
||||
NProgress.start();
|
||||
NProgress.set(0.4);
|
||||
//Increment
|
||||
var interval = setInterval(function () {
|
||||
NProgress.inc();
|
||||
}, 1000);
|
||||
$(document).ready(function () {
|
||||
NProgress.done();
|
||||
clearInterval(interval);
|
||||
});
|
||||
|
||||
|
||||
|
||||
/** 侧边栏回到顶部 */
|
||||
var rocket = $('#rocket');
|
||||
|
||||
$(window).on('scroll', debounce(slideTopSet, 300));
|
||||
|
||||
function debounce(func, wait) {
|
||||
var timeout;
|
||||
return function() {
|
||||
clearTimeout(timeout);
|
||||
timeout = setTimeout(func, wait);
|
||||
};
|
||||
};
|
||||
function slideTopSet() {
|
||||
var top = $(document).scrollTop();
|
||||
|
||||
if (top > 200) {
|
||||
rocket.addClass('show');
|
||||
} else {
|
||||
rocket.removeClass('show');
|
||||
}
|
||||
}
|
||||
$(document).on('click', '#rocket', function(event) {
|
||||
rocket.addClass('move');
|
||||
$('body, html').animate({
|
||||
scrollTop: 0
|
||||
}, 800);
|
||||
});
|
||||
$(document).on('animationEnd', function() {
|
||||
setTimeout(function() {
|
||||
rocket.removeClass('move');
|
||||
}, 400);
|
||||
|
||||
});
|
||||
$(document).on('webkitAnimationEnd', function() {
|
||||
setTimeout(function() {
|
||||
rocket.removeClass('move');
|
||||
}, 400);
|
||||
});
|
||||
|
||||
@@ -0,0 +1,480 @@
|
||||
/* NProgress, (c) 2013, 2014 Rico Sta. Cruz - http://ricostacruz.com/nprogress
|
||||
* @license MIT */
|
||||
|
||||
;(function(root, factory) {
|
||||
|
||||
if (typeof define === 'function' && define.amd) {
|
||||
define(factory);
|
||||
} else if (typeof exports === 'object') {
|
||||
module.exports = factory();
|
||||
} else {
|
||||
root.NProgress = factory();
|
||||
}
|
||||
|
||||
})(this, function() {
|
||||
var NProgress = {};
|
||||
|
||||
NProgress.version = '0.2.0';
|
||||
|
||||
var Settings = NProgress.settings = {
|
||||
minimum: 0.08,
|
||||
easing: 'linear',
|
||||
positionUsing: '',
|
||||
speed: 200,
|
||||
trickle: true,
|
||||
trickleSpeed: 200,
|
||||
showSpinner: true,
|
||||
barSelector: '[role="bar"]',
|
||||
spinnerSelector: '[role="spinner"]',
|
||||
parent: 'body',
|
||||
template: '<div class="bar" role="bar"><div class="peg"></div></div><div class="spinner" role="spinner"><div class="spinner-icon"></div></div>'
|
||||
};
|
||||
|
||||
/**
|
||||
* Updates configuration.
|
||||
*
|
||||
* NProgress.configure({
|
||||
* minimum: 0.1
|
||||
* });
|
||||
*/
|
||||
NProgress.configure = function(options) {
|
||||
var key, value;
|
||||
for (key in options) {
|
||||
value = options[key];
|
||||
if (value !== undefined && options.hasOwnProperty(key)) Settings[key] = value;
|
||||
}
|
||||
|
||||
return this;
|
||||
};
|
||||
|
||||
/**
|
||||
* Last number.
|
||||
*/
|
||||
|
||||
NProgress.status = null;
|
||||
|
||||
/**
|
||||
* Sets the progress bar status, where `n` is a number from `0.0` to `1.0`.
|
||||
*
|
||||
* NProgress.set(0.4);
|
||||
* NProgress.set(1.0);
|
||||
*/
|
||||
|
||||
NProgress.set = function(n) {
|
||||
var started = NProgress.isStarted();
|
||||
|
||||
n = clamp(n, Settings.minimum, 1);
|
||||
NProgress.status = (n === 1 ? null : n);
|
||||
|
||||
var progress = NProgress.render(!started),
|
||||
bar = progress.querySelector(Settings.barSelector),
|
||||
speed = Settings.speed,
|
||||
ease = Settings.easing;
|
||||
|
||||
progress.offsetWidth; /* Repaint */
|
||||
|
||||
queue(function(next) {
|
||||
// Set positionUsing if it hasn't already been set
|
||||
if (Settings.positionUsing === '') Settings.positionUsing = NProgress.getPositioningCSS();
|
||||
|
||||
// Add transition
|
||||
css(bar, barPositionCSS(n, speed, ease));
|
||||
|
||||
if (n === 1) {
|
||||
// Fade out
|
||||
css(progress, {
|
||||
transition: 'none',
|
||||
opacity: 1
|
||||
});
|
||||
progress.offsetWidth; /* Repaint */
|
||||
|
||||
setTimeout(function() {
|
||||
css(progress, {
|
||||
transition: 'all ' + speed + 'ms linear',
|
||||
opacity: 0
|
||||
});
|
||||
setTimeout(function() {
|
||||
NProgress.remove();
|
||||
next();
|
||||
}, speed);
|
||||
}, speed);
|
||||
} else {
|
||||
setTimeout(next, speed);
|
||||
}
|
||||
});
|
||||
|
||||
return this;
|
||||
};
|
||||
|
||||
NProgress.isStarted = function() {
|
||||
return typeof NProgress.status === 'number';
|
||||
};
|
||||
|
||||
/**
|
||||
* Shows the progress bar.
|
||||
* This is the same as setting the status to 0%, except that it doesn't go backwards.
|
||||
*
|
||||
* NProgress.start();
|
||||
*
|
||||
*/
|
||||
NProgress.start = function() {
|
||||
if (!NProgress.status) NProgress.set(0);
|
||||
|
||||
var work = function() {
|
||||
setTimeout(function() {
|
||||
if (!NProgress.status) return;
|
||||
NProgress.trickle();
|
||||
work();
|
||||
}, Settings.trickleSpeed);
|
||||
};
|
||||
|
||||
if (Settings.trickle) work();
|
||||
|
||||
return this;
|
||||
};
|
||||
|
||||
/**
|
||||
* Hides the progress bar.
|
||||
* This is the *sort of* the same as setting the status to 100%, with the
|
||||
* difference being `done()` makes some placebo effect of some realistic motion.
|
||||
*
|
||||
* NProgress.done();
|
||||
*
|
||||
* If `true` is passed, it will show the progress bar even if its hidden.
|
||||
*
|
||||
* NProgress.done(true);
|
||||
*/
|
||||
|
||||
NProgress.done = function(force) {
|
||||
if (!force && !NProgress.status) return this;
|
||||
|
||||
return NProgress.inc(0.3 + 0.5 * Math.random()).set(1);
|
||||
};
|
||||
|
||||
/**
|
||||
* Increments by a random amount.
|
||||
*/
|
||||
|
||||
NProgress.inc = function(amount) {
|
||||
var n = NProgress.status;
|
||||
|
||||
if (!n) {
|
||||
return NProgress.start();
|
||||
} else if(n > 1) {
|
||||
return;
|
||||
} else {
|
||||
if (typeof amount !== 'number') {
|
||||
if (n >= 0 && n < 0.2) { amount = 0.1; }
|
||||
else if (n >= 0.2 && n < 0.5) { amount = 0.04; }
|
||||
else if (n >= 0.5 && n < 0.8) { amount = 0.02; }
|
||||
else if (n >= 0.8 && n < 0.99) { amount = 0.005; }
|
||||
else { amount = 0; }
|
||||
}
|
||||
|
||||
n = clamp(n + amount, 0, 0.994);
|
||||
return NProgress.set(n);
|
||||
}
|
||||
};
|
||||
|
||||
NProgress.trickle = function() {
|
||||
return NProgress.inc();
|
||||
};
|
||||
|
||||
/**
|
||||
* Waits for all supplied jQuery promises and
|
||||
* increases the progress as the promises resolve.
|
||||
*
|
||||
* @param $promise jQUery Promise
|
||||
*/
|
||||
(function() {
|
||||
var initial = 0, current = 0;
|
||||
|
||||
NProgress.promise = function($promise) {
|
||||
if (!$promise || $promise.state() === "resolved") {
|
||||
return this;
|
||||
}
|
||||
|
||||
if (current === 0) {
|
||||
NProgress.start();
|
||||
}
|
||||
|
||||
initial++;
|
||||
current++;
|
||||
|
||||
$promise.always(function() {
|
||||
current--;
|
||||
if (current === 0) {
|
||||
initial = 0;
|
||||
NProgress.done();
|
||||
} else {
|
||||
NProgress.set((initial - current) / initial);
|
||||
}
|
||||
});
|
||||
|
||||
return this;
|
||||
};
|
||||
|
||||
})();
|
||||
|
||||
/**
|
||||
* (Internal) renders the progress bar markup based on the `template`
|
||||
* setting.
|
||||
*/
|
||||
|
||||
NProgress.render = function(fromStart) {
|
||||
if (NProgress.isRendered()) return document.getElementById('nprogress');
|
||||
|
||||
addClass(document.documentElement, 'nprogress-busy');
|
||||
|
||||
var progress = document.createElement('div');
|
||||
progress.id = 'nprogress';
|
||||
progress.innerHTML = Settings.template;
|
||||
|
||||
var bar = progress.querySelector(Settings.barSelector),
|
||||
perc = fromStart ? '-100' : toBarPerc(NProgress.status || 0),
|
||||
parent = document.querySelector(Settings.parent),
|
||||
spinner;
|
||||
|
||||
css(bar, {
|
||||
transition: 'all 0 linear',
|
||||
transform: 'translate3d(' + perc + '%,0,0)'
|
||||
});
|
||||
|
||||
if (!Settings.showSpinner) {
|
||||
spinner = progress.querySelector(Settings.spinnerSelector);
|
||||
spinner && removeElement(spinner);
|
||||
}
|
||||
|
||||
if (parent != document.body) {
|
||||
addClass(parent, 'nprogress-custom-parent');
|
||||
}
|
||||
|
||||
parent.appendChild(progress);
|
||||
return progress;
|
||||
};
|
||||
|
||||
/**
|
||||
* Removes the element. Opposite of render().
|
||||
*/
|
||||
|
||||
NProgress.remove = function() {
|
||||
removeClass(document.documentElement, 'nprogress-busy');
|
||||
removeClass(document.querySelector(Settings.parent), 'nprogress-custom-parent');
|
||||
var progress = document.getElementById('nprogress');
|
||||
progress && removeElement(progress);
|
||||
};
|
||||
|
||||
/**
|
||||
* Checks if the progress bar is rendered.
|
||||
*/
|
||||
|
||||
NProgress.isRendered = function() {
|
||||
return !!document.getElementById('nprogress');
|
||||
};
|
||||
|
||||
/**
|
||||
* Determine which positioning CSS rule to use.
|
||||
*/
|
||||
|
||||
NProgress.getPositioningCSS = function() {
|
||||
// Sniff on document.body.style
|
||||
var bodyStyle = document.body.style;
|
||||
|
||||
// Sniff prefixes
|
||||
var vendorPrefix = ('WebkitTransform' in bodyStyle) ? 'Webkit' :
|
||||
('MozTransform' in bodyStyle) ? 'Moz' :
|
||||
('msTransform' in bodyStyle) ? 'ms' :
|
||||
('OTransform' in bodyStyle) ? 'O' : '';
|
||||
|
||||
if (vendorPrefix + 'Perspective' in bodyStyle) {
|
||||
// Modern browsers with 3D support, e.g. Webkit, IE10
|
||||
return 'translate3d';
|
||||
} else if (vendorPrefix + 'Transform' in bodyStyle) {
|
||||
// Browsers without 3D support, e.g. IE9
|
||||
return 'translate';
|
||||
} else {
|
||||
// Browsers without translate() support, e.g. IE7-8
|
||||
return 'margin';
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Helpers
|
||||
*/
|
||||
|
||||
function clamp(n, min, max) {
|
||||
if (n < min) return min;
|
||||
if (n > max) return max;
|
||||
return n;
|
||||
}
|
||||
|
||||
/**
|
||||
* (Internal) converts a percentage (`0..1`) to a bar translateX
|
||||
* percentage (`-100%..0%`).
|
||||
*/
|
||||
|
||||
function toBarPerc(n) {
|
||||
return (-1 + n) * 100;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* (Internal) returns the correct CSS for changing the bar's
|
||||
* position given an n percentage, and speed and ease from Settings
|
||||
*/
|
||||
|
||||
function barPositionCSS(n, speed, ease) {
|
||||
var barCSS;
|
||||
|
||||
if (Settings.positionUsing === 'translate3d') {
|
||||
barCSS = { transform: 'translate3d('+toBarPerc(n)+'%,0,0)' };
|
||||
} else if (Settings.positionUsing === 'translate') {
|
||||
barCSS = { transform: 'translate('+toBarPerc(n)+'%,0)' };
|
||||
} else {
|
||||
barCSS = { 'margin-left': toBarPerc(n)+'%' };
|
||||
}
|
||||
|
||||
barCSS.transition = 'all '+speed+'ms '+ease;
|
||||
|
||||
return barCSS;
|
||||
}
|
||||
|
||||
/**
|
||||
* (Internal) Queues a function to be executed.
|
||||
*/
|
||||
|
||||
var queue = (function() {
|
||||
var pending = [];
|
||||
|
||||
function next() {
|
||||
var fn = pending.shift();
|
||||
if (fn) {
|
||||
fn(next);
|
||||
}
|
||||
}
|
||||
|
||||
return function(fn) {
|
||||
pending.push(fn);
|
||||
if (pending.length == 1) next();
|
||||
};
|
||||
})();
|
||||
|
||||
/**
|
||||
* (Internal) Applies css properties to an element, similar to the jQuery
|
||||
* css method.
|
||||
*
|
||||
* While this helper does assist with vendor prefixed property names, it
|
||||
* does not perform any manipulation of values prior to setting styles.
|
||||
*/
|
||||
|
||||
var css = (function() {
|
||||
var cssPrefixes = [ 'Webkit', 'O', 'Moz', 'ms' ],
|
||||
cssProps = {};
|
||||
|
||||
function camelCase(string) {
|
||||
return string.replace(/^-ms-/, 'ms-').replace(/-([\da-z])/gi, function(match, letter) {
|
||||
return letter.toUpperCase();
|
||||
});
|
||||
}
|
||||
|
||||
function getVendorProp(name) {
|
||||
var style = document.body.style;
|
||||
if (name in style) return name;
|
||||
|
||||
var i = cssPrefixes.length,
|
||||
capName = name.charAt(0).toUpperCase() + name.slice(1),
|
||||
vendorName;
|
||||
while (i--) {
|
||||
vendorName = cssPrefixes[i] + capName;
|
||||
if (vendorName in style) return vendorName;
|
||||
}
|
||||
|
||||
return name;
|
||||
}
|
||||
|
||||
function getStyleProp(name) {
|
||||
name = camelCase(name);
|
||||
return cssProps[name] || (cssProps[name] = getVendorProp(name));
|
||||
}
|
||||
|
||||
function applyCss(element, prop, value) {
|
||||
prop = getStyleProp(prop);
|
||||
element.style[prop] = value;
|
||||
}
|
||||
|
||||
return function(element, properties) {
|
||||
var args = arguments,
|
||||
prop,
|
||||
value;
|
||||
|
||||
if (args.length == 2) {
|
||||
for (prop in properties) {
|
||||
value = properties[prop];
|
||||
if (value !== undefined && properties.hasOwnProperty(prop)) applyCss(element, prop, value);
|
||||
}
|
||||
} else {
|
||||
applyCss(element, args[1], args[2]);
|
||||
}
|
||||
}
|
||||
})();
|
||||
|
||||
/**
|
||||
* (Internal) Determines if an element or space separated list of class names contains a class name.
|
||||
*/
|
||||
|
||||
function hasClass(element, name) {
|
||||
var list = typeof element == 'string' ? element : classList(element);
|
||||
return list.indexOf(' ' + name + ' ') >= 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* (Internal) Adds a class to an element.
|
||||
*/
|
||||
|
||||
function addClass(element, name) {
|
||||
var oldList = classList(element),
|
||||
newList = oldList + name;
|
||||
|
||||
if (hasClass(oldList, name)) return;
|
||||
|
||||
// Trim the opening space.
|
||||
element.className = newList.substring(1);
|
||||
}
|
||||
|
||||
/**
|
||||
* (Internal) Removes a class from an element.
|
||||
*/
|
||||
|
||||
function removeClass(element, name) {
|
||||
var oldList = classList(element),
|
||||
newList;
|
||||
|
||||
if (!hasClass(element, name)) return;
|
||||
|
||||
// Replace the class name.
|
||||
newList = oldList.replace(' ' + name + ' ', ' ');
|
||||
|
||||
// Trim the opening and closing spaces.
|
||||
element.className = newList.substring(1, newList.length - 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* (Internal) Gets a space separated list of the class names on the element.
|
||||
* The list is wrapped with a single space on each end to facilitate finding
|
||||
* matches within the list.
|
||||
*/
|
||||
|
||||
function classList(element) {
|
||||
return (' ' + (element && element.className || '') + ' ').replace(/\s+/gi, ' ');
|
||||
}
|
||||
|
||||
/**
|
||||
* (Internal) Removes an element from the DOM.
|
||||
*/
|
||||
|
||||
function removeElement(element) {
|
||||
element && element.parentNode && element.parentNode.removeChild(element);
|
||||
}
|
||||
|
||||
return NProgress;
|
||||
});
|
||||
@@ -19,13 +19,15 @@ from django.template.defaultfilters import stringfilter
|
||||
from django.utils.safestring import mark_safe
|
||||
import random
|
||||
from django.core.urlresolvers import reverse
|
||||
from blog.models import Article, Category, Tag, Links
|
||||
from blog.models import Article, Category, Tag, Links, SideBar
|
||||
from django.utils.encoding import force_text
|
||||
from django.shortcuts import get_object_or_404
|
||||
import hashlib
|
||||
import urllib
|
||||
from comments.models import Comment
|
||||
from DjangoBlog.utils import cache_decorator, logger
|
||||
from DjangoBlog.utils import cache
|
||||
from django.contrib.auth import get_user_model
|
||||
from oauth.models import OAuthUser
|
||||
|
||||
register = template.Library()
|
||||
|
||||
@@ -51,8 +53,8 @@ def datetimeformat(data):
|
||||
@register.filter(is_safe=True)
|
||||
@stringfilter
|
||||
def custom_markdown(content):
|
||||
from DjangoBlog.utils import common_markdown
|
||||
return mark_safe(common_markdown.get_markdown(content))
|
||||
from DjangoBlog.utils import CommonMarkdown
|
||||
return mark_safe(CommonMarkdown.get_markdown(content))
|
||||
|
||||
|
||||
@register.filter(is_safe=True)
|
||||
@@ -68,6 +70,14 @@ def truncatechars_content(content):
|
||||
return truncatechars_html(content, settings.ARTICLE_SUB_LENGTH)
|
||||
|
||||
|
||||
@register.filter(is_safe=True)
|
||||
@stringfilter
|
||||
def truncate(content):
|
||||
from django.utils.html import strip_tags
|
||||
|
||||
return strip_tags(content)[:150]
|
||||
|
||||
|
||||
@register.inclusion_tag('blog/tags/breadcrumb.html')
|
||||
def load_breadcrumb(article):
|
||||
"""
|
||||
@@ -115,12 +125,23 @@ def load_sidebar(user):
|
||||
logger.info('load sidebar')
|
||||
recent_articles = Article.objects.filter(status='p')[:settings.SIDEBAR_ARTICLE_COUNT]
|
||||
sidebar_categorys = Category.objects.all()
|
||||
extra_sidebars = SideBar.objects.filter(is_enable=True).order_by('sequence')
|
||||
most_read_articles = Article.objects.filter(status='p').order_by('-views')[:settings.SIDEBAR_ARTICLE_COUNT]
|
||||
dates = Article.objects.datetimes('created_time', 'month', order='DESC')
|
||||
links = Links.objects.all()
|
||||
commment_list = Comment.objects.order_by('-id')[:settings.SIDEBAR_COMMENT_COUNT]
|
||||
show_adsense = settings.SHOW_GOOGLE_ADSENSE
|
||||
# tags=
|
||||
# 标签云 计算字体大小
|
||||
# 根据总数计算出平均值 大小为 (数目/平均值)*步长
|
||||
increment = 5
|
||||
tags = Tag.objects.all()
|
||||
sidebar_tags = None
|
||||
if tags and len(tags) > 0:
|
||||
s = list(map(lambda t: (t, t.get_article_count()), tags))
|
||||
count = sum(map(lambda t: t[1], s))
|
||||
dd = 1 if count == 0 else count / len(tags)
|
||||
sidebar_tags = list(map(lambda x: (x[0], x[1], (x[1] / dd) * increment + 10), s))
|
||||
|
||||
return {
|
||||
'recent_articles': recent_articles,
|
||||
'sidebar_categorys': sidebar_categorys,
|
||||
@@ -129,7 +150,9 @@ def load_sidebar(user):
|
||||
'sidabar_links': links,
|
||||
'sidebar_comments': commment_list,
|
||||
'user': user,
|
||||
'show_adsense': show_adsense
|
||||
'show_adsense': show_adsense,
|
||||
'sidebar_tags': sidebar_tags,
|
||||
'extra_sidebars': extra_sidebars
|
||||
}
|
||||
|
||||
|
||||
@@ -158,12 +181,13 @@ def load_pagination_info(page_obj, page_type, tag_name):
|
||||
previous_number = page_obj.previous_page_number()
|
||||
previous_url = reverse('blog:index_page', kwargs={'page': previous_number})
|
||||
if page_type == '分类标签归档':
|
||||
tag = get_object_or_404(Tag, name=tag_name)
|
||||
if page_obj.has_next():
|
||||
next_number = page_obj.next_page_number()
|
||||
next_url = reverse('blog:tag_detail_page', kwargs={'page': next_number, 'tag_name': tag_name})
|
||||
next_url = reverse('blog:tag_detail_page', kwargs={'page': next_number, 'tag_name': tag.slug})
|
||||
if page_obj.has_previous():
|
||||
previous_number = page_obj.previous_page_number()
|
||||
previous_url = reverse('blog:tag_detail_page', kwargs={'page': previous_number, 'tag_name': tag_name})
|
||||
previous_url = reverse('blog:tag_detail_page', kwargs={'page': previous_number, 'tag_name': tag.slug})
|
||||
if page_type == '作者文章归档':
|
||||
if page_obj.has_next():
|
||||
next_number = page_obj.next_page_number()
|
||||
@@ -173,17 +197,20 @@ def load_pagination_info(page_obj, page_type, tag_name):
|
||||
previous_url = reverse('blog:author_detail_page', kwargs={'page': previous_number, 'author_name': tag_name})
|
||||
|
||||
if page_type == '分类目录归档':
|
||||
category = get_object_or_404(Category, name=tag_name)
|
||||
if page_obj.has_next():
|
||||
next_number = page_obj.next_page_number()
|
||||
next_url = reverse('blog:category_detail_page', kwargs={'page': next_number, 'category_name': tag_name})
|
||||
next_url = reverse('blog:category_detail_page',
|
||||
kwargs={'page': next_number, 'category_name': category.slug})
|
||||
if page_obj.has_previous():
|
||||
previous_number = page_obj.previous_page_number()
|
||||
previous_url = reverse('blog:category_detail_page', kwargs={'page': previous_number, 'category_name': tag_name})
|
||||
previous_url = reverse('blog:category_detail_page',
|
||||
kwargs={'page': previous_number, 'category_name': category.slug})
|
||||
|
||||
return {
|
||||
'previous_url': previous_url,
|
||||
'next_url': next_url,
|
||||
'page_obj':page_obj
|
||||
'page_obj': page_obj
|
||||
}
|
||||
|
||||
|
||||
@@ -217,9 +244,14 @@ def load_article_detail(article, isindex, user):
|
||||
@register.filter
|
||||
def gravatar_url(email, size=40):
|
||||
"""获得gravatar头像"""
|
||||
usermodels = OAuthUser.objects.filter(email=email)
|
||||
if usermodels:
|
||||
o = list(filter(lambda x: x.picture is not None, usermodels))
|
||||
if o:
|
||||
return o[0].picture
|
||||
email = email.encode('utf-8')
|
||||
|
||||
default = "https://avatar.duoshuo.com/avatar-50/928/120117.jpg".encode('utf-8')
|
||||
default = "https://resource.lylinux.net/image/2017/03/26/120117.jpg".encode('utf-8')
|
||||
|
||||
return "https://www.gravatar.com/avatar/%s?%s" % (
|
||||
hashlib.md5(email.lower()).hexdigest(), urllib.parse.urlencode({'d': default, 's': str(size)}))
|
||||
@@ -243,39 +275,3 @@ def query(qs, **kwargs):
|
||||
{% endfor %}
|
||||
"""
|
||||
return qs.filter(**kwargs)
|
||||
|
||||
|
||||
"""
|
||||
article = Article.objects.get(pk=4)
|
||||
comments = Comment.objects.filter(article=article)
|
||||
for c in comments.filter(parent_comment=None):
|
||||
datas = parse_commenttree(comments, c)
|
||||
print(datas)
|
||||
"""
|
||||
"""
|
||||
@register.tag
|
||||
def parseCategoryName(parser,token):
|
||||
tag_name, category = token.split_contents()
|
||||
print(category)
|
||||
print(tag_name)
|
||||
return CategoryNametag(category)
|
||||
|
||||
class CategoryNametag(template.Node):
|
||||
def __init__(self,category):
|
||||
self.category=category
|
||||
self.names=[]
|
||||
|
||||
|
||||
def parseCategory(self,category):
|
||||
self.names.append(category.name)
|
||||
if category.parent_category:
|
||||
self.parseCategory(category.parent_category)
|
||||
|
||||
|
||||
def render(self, context):
|
||||
self.parseCategory(self.category)
|
||||
print(self.names)
|
||||
return " > ".join(self.names)
|
||||
|
||||
#if self.category.parent_category:
|
||||
"""
|
||||
|
||||
+123
-9
@@ -1,8 +1,15 @@
|
||||
from django.test import Client, RequestFactory, TestCase
|
||||
from blog.models import Article, Category, Tag
|
||||
from blog.models import Article, Category, Tag, SideBar
|
||||
from django.contrib.auth import get_user_model
|
||||
from django.contrib.sites.models import Site
|
||||
from blog.forms import BlogSearchForm
|
||||
from django.core.paginator import Paginator
|
||||
from blog.templatetags.blog_tags import load_pagination_info, load_articletags
|
||||
import datetime
|
||||
from accounts.models import BlogUser
|
||||
from django.core.files.uploadedfile import SimpleUploadedFile
|
||||
from django.conf import settings
|
||||
import os
|
||||
|
||||
|
||||
# Create your tests here.
|
||||
@@ -13,25 +20,31 @@ class ArticleTest(TestCase):
|
||||
self.factory = RequestFactory()
|
||||
|
||||
def test_validate_article(self):
|
||||
from accounts.models import BlogUser
|
||||
site = Site.objects.get_current().domain
|
||||
user = BlogUser()
|
||||
user.email = "liangliangyy@gmail.com"
|
||||
user.username = "liangliangyy"
|
||||
user.password = "liangliangyy"
|
||||
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)
|
||||
|
||||
s = SideBar()
|
||||
s.sequence = 1
|
||||
s.name = 'test'
|
||||
s.content = 'test content'
|
||||
s.is_enable = True
|
||||
s.save()
|
||||
|
||||
category = Category()
|
||||
category.name = "category"
|
||||
category.created_time = datetime.datetime.now()
|
||||
category.last_mod_time = datetime.datetime.now()
|
||||
category.save()
|
||||
|
||||
response = self.client.get(category.get_absolute_url())
|
||||
self.assertEqual(response.status_code, 200)
|
||||
tag = Tag()
|
||||
tag.name = "nicetag"
|
||||
tag.save()
|
||||
|
||||
article = Article()
|
||||
article.title = "nicetitle"
|
||||
@@ -40,6 +53,107 @@ class ArticleTest(TestCase):
|
||||
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()
|
||||
response = self.client.get(article.get_absolute_url())
|
||||
self.assertEqual(response.status_code, 200)
|
||||
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)
|
||||
|
||||
p = Paginator(Article.objects.all(), 2)
|
||||
self.__check_pagination__(p, '', '')
|
||||
|
||||
p = Paginator(Article.objects.filter(tags=tag), 2)
|
||||
self.__check_pagination__(p, '分类标签归档', tag.slug)
|
||||
|
||||
p = Paginator(Article.objects.filter(author__username='liangliangyy'), 2)
|
||||
self.__check_pagination__(p, '作者文章归档', 'liangliangyy')
|
||||
|
||||
p = Paginator(Article.objects.filter(category=category), 2)
|
||||
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()])
|
||||
rsp = self.client.get('/refresh/')
|
||||
self.assertEqual(rsp.status_code, 200)
|
||||
from blog.templatetags.blog_tags import gravatar_url, gravatar
|
||||
u = gravatar_url('liangliangyy@gmail.com')
|
||||
u = gravatar('liangliangyy@gmail.com')
|
||||
|
||||
def __check_pagination__(self, p, type, value):
|
||||
s = load_pagination_info(p.page(1), type, value)
|
||||
self.assertIsNotNone(s)
|
||||
response = self.client.get(s['previous_url'])
|
||||
self.assertEqual(response.status_code, 200)
|
||||
response = self.client.get(s['next_url'])
|
||||
self.assertEqual(response.status_code, 200)
|
||||
|
||||
s = load_pagination_info(p.page(2), type, value)
|
||||
self.assertIsNotNone(s)
|
||||
response = self.client.get(s['previous_url'])
|
||||
self.assertEqual(response.status_code, 200)
|
||||
response = self.client.get(s['next_url'])
|
||||
self.assertEqual(response.status_code, 200)
|
||||
|
||||
def test_validate_feed(self):
|
||||
user = BlogUser.objects.get_or_create(email="liangliangyy12@gmail.com", username="liangliangyy")[0]
|
||||
user.set_password("liangliangyy")
|
||||
user.save()
|
||||
|
||||
response = self.client.get('/feed/')
|
||||
self.assertEqual(response.status_code, 200)
|
||||
|
||||
response = self.client.get('/sitemap.xml')
|
||||
self.assertEqual(response.status_code, 200)
|
||||
|
||||
def test_image(self):
|
||||
import requests
|
||||
rsp = requests.get('https://ss2.baidu.com/6ONYsjip0QIZ8tyhnq/it/u=2909203028,3998034658&fm=96')
|
||||
imagepath = os.path.join(settings.BASE_DIR, 'django.jpg')
|
||||
with open(imagepath, 'wb') as file:
|
||||
file.write(rsp.content)
|
||||
with open(imagepath, 'rb') as file:
|
||||
imgfile = SimpleUploadedFile('django.jpg', file.read(), content_type='image/jpg')
|
||||
form_data = {'django.jpg': imgfile}
|
||||
rsp = self.client.post('/upload', form_data, follow=True)
|
||||
|
||||
self.assertEqual(rsp.status_code, 200)
|
||||
"""
|
||||
data = SimpleUploadedFile(imagepath, b'file_content', content_type='image/jpg')
|
||||
rsp = self.client.post('/upload', {'django.jpg': data})
|
||||
self.assertEqual(rsp.status_code, 200)
|
||||
SimpleUploadedFile()
|
||||
"""
|
||||
|
||||
def test_errorpage(self):
|
||||
self.client.get('/eee')
|
||||
self.client.get('/refresh_memcache')
|
||||
|
||||
+4
-12
@@ -20,21 +20,13 @@ from haystack.forms import ModelSearchForm
|
||||
from haystack.query import SearchQuerySet
|
||||
from haystack.views import SearchView
|
||||
|
||||
|
||||
def key_prefixer(request):
|
||||
# if it's not there, don't cache
|
||||
# return request.GET.get('number')
|
||||
return "123"
|
||||
|
||||
|
||||
urlpatterns = [
|
||||
url(r'^$', views.IndexView.as_view(), name='index'),
|
||||
url(r'^page/(?P<page>\d+)$', views.IndexView.as_view(), name='index_page'),
|
||||
|
||||
url(r'^article/(?P<year>\d+)/(?P<month>\d+)/(?P<day>\d+)/(?P<article_id>\d+)-(?P<slug>\S+).html$',
|
||||
# cache_page(60 * 60 * 10, key_prefix="blogdetail")(views.ArticleDetailView.as_view()),
|
||||
url(r'^article/(?P<year>\d+)/(?P<month>\d+)/(?P<day>\d+)/(?P<article_id>\d+).html$',
|
||||
views.ArticleDetailView.as_view(),
|
||||
name='detail'),
|
||||
name='detailbyid'),
|
||||
|
||||
url(r'^blogpage/(?P<year>\d+)/(?P<month>\d+)/(?P<day>\d+)/(?P<page_id>\d+)-(?P<slug>[\w-]+).html$',
|
||||
views.ArticleDetailView.as_view(),
|
||||
@@ -50,8 +42,8 @@ urlpatterns = [
|
||||
url(r'^author/(?P<author_name>\w+)/(?P<page>\d+).html$', views.AuthorDetailView.as_view(),
|
||||
name='author_detail_page'),
|
||||
|
||||
url(r'^tag/(?P<tag_name>.+).html$', views.TagDetailView.as_view(), name='tag_detail'),
|
||||
url(r'^tag/(?P<tag_name>.+)/(?P<page>\d+).html$', views.TagDetailView.as_view(), name='tag_detail_page'),
|
||||
url(r'^tag/(?P<tag_name>[\w-]+).html$', views.TagDetailView.as_view(), name='tag_detail'),
|
||||
url(r'^tag/(?P<tag_name>[\w-]+)/(?P<page>\d+).html$', views.TagDetailView.as_view(), name='tag_detail_page'),
|
||||
|
||||
url(r'^upload', views.fileupload, name='upload'),
|
||||
url(r'^refresh', views.refresh_memcache, name='refresh')
|
||||
|
||||
+113
-128
@@ -1,60 +1,20 @@
|
||||
from django.shortcuts import render
|
||||
|
||||
# Create your views here.
|
||||
import os
|
||||
import datetime
|
||||
from django.views.generic.list import ListView
|
||||
from django.views.generic import TemplateView
|
||||
from django.views.decorators.cache import cache_page
|
||||
from django.views.generic.detail import DetailView
|
||||
from django.views.generic import UpdateView
|
||||
from django.views.generic.edit import CreateView, FormView
|
||||
from django.views.generic.dates import YearArchiveView, MonthArchiveView
|
||||
from blog.models import Article, Category, Tag
|
||||
from django.core.paginator import Paginator, PageNotAnInteger, EmptyPage
|
||||
from django.core.exceptions import ObjectDoesNotExist
|
||||
from comments.forms import CommentForm
|
||||
from django.conf import settings
|
||||
from django import forms
|
||||
from django import http
|
||||
from django.http import HttpResponse
|
||||
from abc import ABCMeta, abstractmethod
|
||||
from haystack.generic_views import SearchView
|
||||
from blog.forms import BlogSearchForm
|
||||
import datetime
|
||||
from django.http import HttpResponseRedirect
|
||||
from django.views.decorators.csrf import csrf_exempt
|
||||
import os
|
||||
from django.contrib.auth.decorators import login_required
|
||||
from DjangoBlog.utils import cache, cache_decorator
|
||||
from django.utils.cache import get_cache_key
|
||||
from django.utils.decorators import classonlymethod
|
||||
from django.utils.decorators import method_decorator
|
||||
from DjangoBlog.utils import cache, logger
|
||||
from django.shortcuts import get_object_or_404
|
||||
|
||||
"""
|
||||
class SeoProcessor():
|
||||
__metaclass__ = ABCMeta
|
||||
|
||||
@abstractmethod
|
||||
def get_title(self):
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def get_keywords(self):
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def get_description(self):
|
||||
pass
|
||||
"""
|
||||
|
||||
"""
|
||||
class CachedTemplateView(ListView):
|
||||
@classonlymethod
|
||||
def as_view(cls, **initkwargs):
|
||||
# print(request)
|
||||
|
||||
view = super(CachedTemplateView, cls).as_view(**initkwargs)
|
||||
return cache_page(60 * 60 * 10)(view)
|
||||
"""
|
||||
from blog.models import Article, Category, Tag
|
||||
from comments.forms import CommentForm
|
||||
|
||||
|
||||
class ArticleListView(ListView):
|
||||
@@ -72,12 +32,51 @@ class ArticleListView(ListView):
|
||||
def get_view_cache_key(self):
|
||||
return self.request.get['pages']
|
||||
|
||||
@property
|
||||
def page_number(self):
|
||||
page_kwarg = self.page_kwarg
|
||||
page = self.kwargs.get(page_kwarg) or self.request.GET.get(page_kwarg) or 1
|
||||
return page
|
||||
|
||||
def get_queryset_cache_key(self):
|
||||
"""
|
||||
子类重写.获得queryset的缓存key
|
||||
"""
|
||||
raise NotImplementedError()
|
||||
|
||||
def get_queryset_data(self):
|
||||
"""
|
||||
子类重写.获取queryset的数据
|
||||
"""
|
||||
raise NotImplementedError()
|
||||
|
||||
def get_queryset_from_cache(self, cache_key):
|
||||
# raise NotImplementedError()
|
||||
value = cache.get(cache_key)
|
||||
if value:
|
||||
logger.info('get view cache.key:{key}'.format(key=cache_key))
|
||||
return value
|
||||
else:
|
||||
article_list = self.get_queryset_data()
|
||||
cache.set(cache_key, article_list)
|
||||
logger.info('set view cache.key:{key}'.format(key=cache_key))
|
||||
return article_list
|
||||
|
||||
def get_queryset(self):
|
||||
key = self.get_queryset_cache_key()
|
||||
value = self.get_queryset_from_cache(key)
|
||||
return value
|
||||
|
||||
|
||||
class IndexView(ArticleListView):
|
||||
def get_queryset(self):
|
||||
def get_queryset_data(self):
|
||||
article_list = Article.objects.filter(type='a', status='p')
|
||||
return article_list
|
||||
|
||||
def get_queryset_cache_key(self):
|
||||
cache_key = 'index_{page}'.format(page=self.page_number)
|
||||
return cache_key
|
||||
|
||||
|
||||
class ArticleDetailView(DetailView):
|
||||
template_name = 'blog/article_detail.html'
|
||||
@@ -85,17 +84,14 @@ class ArticleDetailView(DetailView):
|
||||
pk_url_kwarg = 'article_id'
|
||||
context_object_name = "article"
|
||||
|
||||
def get_object(self):
|
||||
def get_object(self, queryset=None):
|
||||
obj = super(ArticleDetailView, self).get_object()
|
||||
|
||||
obj.viewed()
|
||||
# obj.body = markdown2.markdown(obj.body)
|
||||
self.object = obj
|
||||
return obj
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
articleid = int(self.kwargs[self.pk_url_kwarg])
|
||||
|
||||
comment_form = CommentForm()
|
||||
user = self.request.user
|
||||
|
||||
@@ -107,69 +103,41 @@ class ArticleDetailView(DetailView):
|
||||
comment_form.fields["email"].initial = user.email
|
||||
comment_form.fields["name"].initial = user.username
|
||||
|
||||
article_comments = self.object.comment_set.all()
|
||||
article_comments = self.object.comment_list()
|
||||
|
||||
kwargs['form'] = comment_form
|
||||
kwargs['article_comments'] = article_comments
|
||||
kwargs['comment_count'] = len(article_comments) if article_comments else 0;
|
||||
kwargs['comment_count'] = len(article_comments) if article_comments else 0
|
||||
|
||||
kwargs['next_article'] = self.object.next_article
|
||||
kwargs['prev_article'] = self.object.prev_article
|
||||
|
||||
return super(ArticleDetailView, self).get_context_data(**kwargs)
|
||||
|
||||
"""
|
||||
@classonlymethod
|
||||
def as_view(cls, **initkwargs):
|
||||
self = cls(**initkwargs)
|
||||
keyperfix = "blogdetail"
|
||||
return cache_page(60 * 60 * 10, key_prefix=keyperfix)(super(ArticleDetailView, cls).as_view(**initkwargs))
|
||||
"""
|
||||
|
||||
|
||||
"""
|
||||
def post(self, request, *args, **kwargs):
|
||||
form = CommentForm(request.POST)
|
||||
|
||||
if form.is_valid():
|
||||
data = form.cleaned_data
|
||||
pass
|
||||
"""
|
||||
|
||||
'''
|
||||
class PageDetailView(ArticleDetailView):
|
||||
model = BlogPage
|
||||
pk_url_kwarg = 'page_id'
|
||||
|
||||
def get_object(self):
|
||||
obj = super(PageDetailView, self).get_object()
|
||||
print(obj.title)
|
||||
obj.viewed()
|
||||
# obj.body = markdown2.markdown(obj.body)
|
||||
return obj
|
||||
'''
|
||||
|
||||
|
||||
class CategoryDetailView(ArticleListView):
|
||||
page_type = "分类目录归档"
|
||||
|
||||
def get_queryset(self):
|
||||
def get_queryset_data(self):
|
||||
slug = self.kwargs['category_name']
|
||||
category = get_object_or_404(Category, slug=slug)
|
||||
|
||||
categoryname = category.name
|
||||
self.categoryname = categoryname
|
||||
categorynames = list(map(lambda c: c.name, category.get_sub_categorys()))
|
||||
article_list = Article.objects.filter(category__name__in=categorynames, status='p')
|
||||
return article_list
|
||||
|
||||
def get_queryset_cache_key(self):
|
||||
slug = self.kwargs['category_name']
|
||||
# category = Category.objects.get(slug=slug)
|
||||
category = get_object_or_404(Category, slug=slug)
|
||||
categoryname = category.name
|
||||
self.categoryname = categoryname
|
||||
try:
|
||||
categoryname = categoryname.split('/')[-1]
|
||||
except:
|
||||
pass
|
||||
article_list = Article.objects.filter(category__name=categoryname, status='p')
|
||||
return article_list
|
||||
cache_key = 'category_list_{categoryname}_{page}'.format(categoryname=categoryname, page=self.page_number)
|
||||
return cache_key
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
# slug = self.kwargs['category_name']
|
||||
# category = Category.objects.get(slug=slug)
|
||||
# categoryname = category.name
|
||||
|
||||
categoryname = self.categoryname
|
||||
try:
|
||||
categoryname = categoryname.split('/')[-1]
|
||||
@@ -183,7 +151,12 @@ class CategoryDetailView(ArticleListView):
|
||||
class AuthorDetailView(ArticleListView):
|
||||
page_type = '作者文章归档'
|
||||
|
||||
def get_queryset(self):
|
||||
def get_queryset_cache_key(self):
|
||||
author_name = self.kwargs['author_name']
|
||||
cache_key = 'author_{author_name}_{page}'.format(author_name=author_name, page=self.page_number)
|
||||
return cache_key
|
||||
|
||||
def get_queryset_data(self):
|
||||
author_name = self.kwargs['author_name']
|
||||
article_list = Article.objects.filter(author__username=author_name)
|
||||
return article_list
|
||||
@@ -209,15 +182,22 @@ class TagListView(ListView):
|
||||
class TagDetailView(ArticleListView):
|
||||
page_type = '分类标签归档'
|
||||
|
||||
def get_queryset(self):
|
||||
def get_queryset_data(self):
|
||||
slug = self.kwargs['tag_name']
|
||||
# tag = Tag.objects.get(slug=slug)
|
||||
tag = get_object_or_404(Tag, slug=slug)
|
||||
tag_name = tag.name
|
||||
self.name = tag_name
|
||||
article_list = Article.objects.filter(tags__name=tag_name)
|
||||
return article_list
|
||||
|
||||
def get_queryset_cache_key(self):
|
||||
slug = self.kwargs['tag_name']
|
||||
tag = get_object_or_404(Tag, slug=slug)
|
||||
tag_name = tag.name
|
||||
self.name = tag_name
|
||||
cache_key = 'tag_{tag_name}_{page}'.format(tag_name=tag_name, page=self.page_number)
|
||||
return cache_key
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
# tag_name = self.kwargs['tag_name']
|
||||
tag_name = self.name
|
||||
@@ -229,19 +209,32 @@ class TagDetailView(ArticleListView):
|
||||
@csrf_exempt
|
||||
def fileupload(request):
|
||||
if request.method == 'POST':
|
||||
fname = ''
|
||||
timestr = datetime.datetime.now().strftime('%Y/%m/%d')
|
||||
basepath = os.path.join(r'/var/www/resource/image/', timestr)
|
||||
if not os.path.exists(basepath):
|
||||
os.makedirs(basepath)
|
||||
fname = ''
|
||||
response = []
|
||||
for filename in request.FILES:
|
||||
fname = filename
|
||||
timestr = datetime.datetime.now().strftime('%Y/%m/%d')
|
||||
imgextensions = ['jpg', 'png', 'jpeg', 'bmp']
|
||||
fname = u''.join(str(filename))
|
||||
|
||||
isimage = len([i for i in imgextensions if fname.find(i) >= 0]) > 0
|
||||
|
||||
basepath = r'/var/www/resource/{type}/{timestr}'.format(
|
||||
type='files' if not isimage else'image', timestr=timestr)
|
||||
if settings.TESTING:
|
||||
basepath = settings.BASE_DIR + '/uploads'
|
||||
url = 'https://resource.lylinux.net/{type}/{timestr}/{filename}'.format(
|
||||
type='files' if not isimage else'image', timestr=timestr, filename=filename)
|
||||
if not os.path.exists(basepath):
|
||||
os.makedirs(basepath)
|
||||
savepath = os.path.join(basepath, filename)
|
||||
with open(savepath, 'wb+') as wfile:
|
||||
for chunk in request.FILES[filename].chunks():
|
||||
wfile.write(chunk)
|
||||
return HttpResponse('https://resource.lylinux.net/' + 'image/' + timestr + '/' + fname)
|
||||
if isimage:
|
||||
from PIL import Image
|
||||
image = Image.open(savepath)
|
||||
image.save(savepath, quality=20, optimize=True)
|
||||
response.append(url)
|
||||
return HttpResponse(response)
|
||||
|
||||
else:
|
||||
return HttpResponse("only for post")
|
||||
@@ -253,35 +246,27 @@ def refresh_memcache(request):
|
||||
|
||||
if request.user.is_superuser:
|
||||
from DjangoBlog.utils import cache
|
||||
if cache != None: cache.clear()
|
||||
if cache and cache is not None:
|
||||
cache.clear()
|
||||
return HttpResponse("ok")
|
||||
else:
|
||||
from django.http import HttpResponseForbidden
|
||||
return HttpResponseForbidden()
|
||||
except Exception as e:
|
||||
return HttpResponse(e);
|
||||
return HttpResponse(e)
|
||||
|
||||
|
||||
"""
|
||||
class BlogSearchView(SearchView):
|
||||
form_class = BlogSearchForm
|
||||
template_name = 'blog/article_detail.html'
|
||||
model = Article
|
||||
# template_name属性用于指定使用哪个模板进行渲染
|
||||
template_name = 'blog/article_index.html'
|
||||
def page_not_found_view(request):
|
||||
url = request.get_full_path()
|
||||
return render(request, 'blog/error_page.html',
|
||||
{'message': '哎呀,您访问的地址 ' + url + ' 是一个未知的地方。请点击首页看看别的?', 'statuscode': '404'})
|
||||
|
||||
# context_object_name属性用于给上下文变量取名(在模板中使用该名字)
|
||||
context_object_name = 'article_list'
|
||||
|
||||
def get_queryset(self):
|
||||
queryset = super(BlogSearchView, self).get_queryset()
|
||||
# further filter queryset based on some set of criteria
|
||||
# return queryset.filter(pub_date__gte=date(2015, 1, 1))
|
||||
return queryset
|
||||
def server_error_view(request):
|
||||
return render(request, 'blog/error_page.html',
|
||||
{'message': '哎呀,出错了,我已经收集到了错误信息,之后会抓紧抢修,请点击首页看看别的?', 'statuscode': '500'})
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
tag_name = 'search'
|
||||
kwargs['page_type'] = 'search'
|
||||
kwargs['tag_name'] = tag_name
|
||||
return super(BlogSearchView, self).get_context_data(**kwargs)
|
||||
"""
|
||||
|
||||
def permission_denied_view(request):
|
||||
return render(request, 'blog/error_page.html',
|
||||
{'message': '哎呀,您没有权限访问此页面,请点击首页看看别的?', 'statuscode': '403'})
|
||||
|
||||
+9
-1
@@ -3,4 +3,12 @@ from django.contrib import admin
|
||||
# Register your models here.
|
||||
from .models import Comment
|
||||
|
||||
admin.site.register(Comment)
|
||||
|
||||
class CommentAdmin(admin.ModelAdmin):
|
||||
list_display = ('id', 'body', 'author', 'article', 'last_mod_time')
|
||||
list_display_links = ('id', 'body')
|
||||
list_filter = ('author', 'article',)
|
||||
exclude = ('created_time', 'last_mod_time')
|
||||
|
||||
|
||||
admin.site.register(Comment, CommentAdmin)
|
||||
|
||||
@@ -18,28 +18,6 @@ from django.forms import ModelForm
|
||||
from django.contrib.auth.models import User
|
||||
from django.contrib.auth import get_user_model
|
||||
|
||||
"""
|
||||
class CommentForm(forms.Form):
|
||||
url = forms.URLField(label='网址', required=False)
|
||||
email = forms.EmailField(label='电子邮箱', required=False)
|
||||
name = forms.CharField(label='姓名')
|
||||
body = forms.CharField(widget=forms.Textarea, label='评论')
|
||||
parent_comment_id = forms.IntegerField(widget=forms.HiddenInput, required=False)
|
||||
|
||||
|
||||
|
||||
class LoginCommentForm(ModelForm):
|
||||
url = forms.URLField(label='网址', required=False)
|
||||
email = forms.EmailField(label='电子邮箱', required=False, widget=forms.HiddenInput)
|
||||
name = forms.CharField(label='姓名', widget=forms.HiddenInput)
|
||||
parent_comment_id = forms.IntegerField(widget=forms.HiddenInput, required=False)
|
||||
def __init__(self):
|
||||
pass
|
||||
class Meta:
|
||||
model = Comment
|
||||
fields = ['body']
|
||||
"""
|
||||
|
||||
|
||||
class CommentForm(ModelForm):
|
||||
url = forms.URLField(label='网址', required=False)
|
||||
@@ -49,21 +27,6 @@ class CommentForm(ModelForm):
|
||||
'aria-required': 'true'}
|
||||
))
|
||||
parent_comment_id = forms.IntegerField(widget=forms.HiddenInput, required=False)
|
||||
"""
|
||||
if get_user_model().is_authenticated:
|
||||
email = forms.EmailField(label='电子邮箱', required=False, widget=forms.HiddenInput)
|
||||
name = forms.CharField(label='姓名', widget=forms.HiddenInput)
|
||||
"""
|
||||
"""
|
||||
def __init__(self, user, *args, **kwargs):
|
||||
self.user = user
|
||||
super(CommentForm, self).__init__(*args, **kwargs)
|
||||
if self.user.is_authenticated:
|
||||
self.fields.update({
|
||||
'email': forms.CharField(widget=forms.HiddenInput()),
|
||||
'name': forms.CharField(widget=forms.HiddenInput()),
|
||||
})
|
||||
"""
|
||||
|
||||
class Meta:
|
||||
model = Comment
|
||||
|
||||
+7
-51
@@ -1,22 +1,16 @@
|
||||
from django.db import models
|
||||
from django.conf import settings
|
||||
from django.core.mail import send_mail
|
||||
from blog.models import Article
|
||||
from django.core.mail import EmailMultiAlternatives
|
||||
from django.contrib.sites.models import Site
|
||||
import _thread
|
||||
from DjangoBlog.utils import cache
|
||||
from blog.models import Article
|
||||
from DjangoBlog.utils import logger
|
||||
from django.utils.timezone import now
|
||||
|
||||
|
||||
# Create your models here.
|
||||
|
||||
class Comment(models.Model):
|
||||
# url = models.URLField('地址', blank=True, null=True)
|
||||
# email = models.EmailField('电子邮件', blank=True, null=True)
|
||||
|
||||
body = models.TextField('正文', max_length=300)
|
||||
created_time = models.DateTimeField('创建时间', auto_now_add=True)
|
||||
last_mod_time = models.DateTimeField('修改时间', auto_now=True)
|
||||
created_time = models.DateTimeField('创建时间', default=now)
|
||||
last_mod_time = models.DateTimeField('修改时间', default=now)
|
||||
author = models.ForeignKey(settings.AUTH_USER_MODEL, verbose_name='作者', on_delete=models.CASCADE)
|
||||
article = models.ForeignKey(Article, verbose_name='文章', on_delete=models.CASCADE)
|
||||
parent_comment = models.ForeignKey('self', verbose_name="上级评论", blank=True, null=True)
|
||||
@@ -27,46 +21,8 @@ class Comment(models.Model):
|
||||
verbose_name_plural = verbose_name
|
||||
get_latest_by = 'created_time'
|
||||
|
||||
def send_comment_email(self, msg):
|
||||
try:
|
||||
msg.send()
|
||||
except:
|
||||
pass
|
||||
def __str__(self):
|
||||
return self.body
|
||||
|
||||
def save(self, *args, **kwargs):
|
||||
super().save(*args, **kwargs)
|
||||
subject = '感谢您发表的评论'
|
||||
site = Site.objects.get_current().domain
|
||||
article_url = "https://{site}{path}".format(site=site, path=self.article.get_absolute_url())
|
||||
html_content = """
|
||||
<p>非常感谢您在本站发表评论</p>
|
||||
您可以访问
|
||||
<a href="%s" rel="bookmark">%s</a>
|
||||
来查看您的评论,
|
||||
再次感谢您!
|
||||
<br />
|
||||
如果上面链接无法打开,请将此链接复制至浏览器。
|
||||
%s
|
||||
""" % (article_url, self.article.title, article_url)
|
||||
tomail = self.author.email
|
||||
msg = EmailMultiAlternatives(subject, html_content, from_email='no-reply@lylinux.net', to=[tomail])
|
||||
|
||||
msg.content_subtype = "html"
|
||||
|
||||
_thread.start_new_thread(self.send_comment_email, (msg,))
|
||||
|
||||
if self.parent_comment:
|
||||
html_content = """
|
||||
您在 <a href="%s" rel="bookmark">%s</a> 的评论 <br/> %s <br/> 收到回复啦.快去看看吧
|
||||
<br/>
|
||||
如果上面链接无法打开,请将此链接复制至浏览器。
|
||||
%s
|
||||
""" % (article_url, self.article.title, self.parent_comment.body, article_url)
|
||||
tomail = self.parent_comment.author.email
|
||||
msg = EmailMultiAlternatives(subject, html_content, from_email='no-reply@lylinux.net', to=[tomail])
|
||||
msg.content_subtype = "html"
|
||||
|
||||
_thread.start_new_thread(self.send_comment_email, (msg,))
|
||||
|
||||
def __str__(self):
|
||||
return self.body
|
||||
|
||||
@@ -30,7 +30,6 @@ def parse_commenttree(commentlist, comment):
|
||||
datas = []
|
||||
|
||||
def parse(c):
|
||||
|
||||
childs = commentlist.filter(parent_comment=c)
|
||||
for child in childs:
|
||||
datas.append(child)
|
||||
@@ -43,31 +42,8 @@ def parse_commenttree(commentlist, comment):
|
||||
@register.inclusion_tag('comments/tags/comment_item.html')
|
||||
def show_comment_item(comment, ischild):
|
||||
"""评论"""
|
||||
depth = 1 if ischild else 2;
|
||||
depth = 1 if ischild else 2
|
||||
return {
|
||||
'comment_item': comment,
|
||||
'depth': depth
|
||||
}
|
||||
|
||||
|
||||
"""
|
||||
@register.simple_tag(name='get_comment_count')
|
||||
def GetCommentCount(parser, token):
|
||||
commentcount = Comment.objects.filter(article__author_id=token).count()
|
||||
return "0" if commentcount == 0 else str(commentcount) + " comments"
|
||||
|
||||
|
||||
|
||||
@register.inclusion_tag('comments/tags/post_comment.html')
|
||||
def load_post_comment(article, lastform=None):
|
||||
if not lastform:
|
||||
form = CommentForm()
|
||||
form.article_id = article.id
|
||||
form.parent_comment_id = ''
|
||||
else:
|
||||
form = lastform
|
||||
return {
|
||||
'article': article,
|
||||
'form': form
|
||||
}
|
||||
"""
|
||||
|
||||
+95
-2
@@ -1,3 +1,96 @@
|
||||
from django.test import TestCase
|
||||
from django.test import Client, RequestFactory, TestCase
|
||||
from blog.models import Article, Category, Tag
|
||||
from django.contrib.auth import get_user_model
|
||||
from django.contrib.sites.models import Site
|
||||
from django.core.urlresolvers import reverse
|
||||
import datetime
|
||||
from accounts.models import BlogUser
|
||||
from comments.templatetags.comments_tags import *
|
||||
from DjangoBlog.utils import get_max_articleid_commentid
|
||||
|
||||
# Create your tests here.
|
||||
|
||||
# Create your tests here.
|
||||
|
||||
class CommentsTest(TestCase):
|
||||
def setUp(self):
|
||||
self.client = Client()
|
||||
self.factory = RequestFactory()
|
||||
|
||||
def test_validate_comment(self):
|
||||
site = Site.objects.get_current().domain
|
||||
user = BlogUser.objects.create_superuser(email="liangliangyy1@gmail.com",
|
||||
username="liangliangyy1", password="liangliangyy1")
|
||||
|
||||
self.client.login(username='liangliangyy1', password='liangliangyy1')
|
||||
|
||||
category = Category()
|
||||
category.name = "categoryccc"
|
||||
category.created_time = datetime.datetime.now()
|
||||
category.last_mod_time = datetime.datetime.now()
|
||||
category.save()
|
||||
|
||||
article = Article()
|
||||
article.title = "nicetitleccc"
|
||||
article.body = "nicecontentccc"
|
||||
article.author = user
|
||||
article.category = category
|
||||
article.type = 'a'
|
||||
article.status = 'p'
|
||||
article.save()
|
||||
|
||||
commenturl = reverse('comments:postcomment', kwargs={'article_id': article.id})
|
||||
|
||||
response = self.client.post(commenturl,
|
||||
{
|
||||
'body': '123ffffffffff'
|
||||
})
|
||||
|
||||
self.assertEqual(response.status_code, 200)
|
||||
|
||||
article = Article.objects.get(pk=article.pk)
|
||||
self.assertEqual(len(article.comment_list()), 0)
|
||||
|
||||
response = self.client.post(commenturl,
|
||||
{
|
||||
'body': '123ffffffffff',
|
||||
'email': user.email,
|
||||
'name': user.username
|
||||
})
|
||||
|
||||
self.assertEqual(response.status_code, 302)
|
||||
|
||||
article = Article.objects.get(pk=article.pk)
|
||||
self.assertEqual(len(article.comment_list()), 1)
|
||||
parent_comment_id = article.comment_list()[0].id
|
||||
|
||||
response = self.client.post(commenturl,
|
||||
{
|
||||
'body': '''
|
||||
# Title1
|
||||
|
||||
```python
|
||||
import os
|
||||
```
|
||||
|
||||
[url](https://www.lylinux.org/)
|
||||
|
||||
[ddd](http://www.baidu.com)
|
||||
|
||||
|
||||
''',
|
||||
'email': user.email,
|
||||
'name': user.username,
|
||||
'parent_comment_id': parent_comment_id
|
||||
})
|
||||
|
||||
self.assertEqual(response.status_code, 302)
|
||||
|
||||
article = Article.objects.get(pk=article.pk)
|
||||
self.assertEqual(len(article.comment_list()), 2)
|
||||
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)
|
||||
|
||||
+9
-17
@@ -9,17 +9,7 @@ from django.http import HttpResponseRedirect
|
||||
from django.contrib.auth import get_user_model
|
||||
from django import forms
|
||||
|
||||
"""
|
||||
from django.core.urlresolvers import reverse
|
||||
from django.contrib import auth
|
||||
from django.utils.decorators import method_decorator
|
||||
from django.views.decorators.cache import never_cache
|
||||
from django.views.decorators.csrf import csrf_protect
|
||||
from django.contrib.auth.decorators import login_required
|
||||
"""
|
||||
|
||||
|
||||
# @method_decorator(login_required,name='dispatch')
|
||||
class CommentPostView(FormView):
|
||||
form_class = CommentForm
|
||||
template_name = 'blog/article_detail.html'
|
||||
@@ -72,12 +62,14 @@ class CommentPostView(FormView):
|
||||
comment.parent_comment = parent_comment
|
||||
|
||||
comment.save(True)
|
||||
from DjangoBlog.utils import expire_view_cache, cache
|
||||
from django.contrib.sites.models import Site
|
||||
path = article.get_absolute_url()
|
||||
site = Site.objects.get_current().domain
|
||||
|
||||
expire_view_cache(path, servername=site, serverport=self.request.get_port(), key_prefix='blogdetail')
|
||||
if cache.get('seo_processor'):
|
||||
cache.delete('seo_processor')
|
||||
from DjangoBlog.blog_signals import comment_save_signal
|
||||
port = 80
|
||||
try:
|
||||
# django1.8 没有这个方法...
|
||||
port = self.request.get_port()
|
||||
except:
|
||||
pass
|
||||
username = self.request.user.username if self.request.user else ''
|
||||
comment_save_signal.send(sender=self.__class__, comment_id=comment.id, username=username, serverport=port)
|
||||
return HttpResponseRedirect("%s#div-comment-%d" % (article.get_absolute_url(), comment.pk))
|
||||
|
||||
BIN
Binary file not shown.
|
Before Width: | Height: | Size: 3.1 KiB |
@@ -1,3 +1,13 @@
|
||||
from django.contrib import admin
|
||||
|
||||
# Register your models here.
|
||||
from .models import OAuthUser
|
||||
|
||||
|
||||
class OAuthUserAdmin(admin.ModelAdmin):
|
||||
list_display = ('id', 'author', 'nikename', 'type', 'picture', 'email',)
|
||||
list_display_links = ('id', 'nikename')
|
||||
list_filter = ('author', 'type',)
|
||||
|
||||
|
||||
admin.site.register(OAuthUser, OAuthUserAdmin)
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
#!/usr/bin/env python
|
||||
# encoding: utf-8
|
||||
|
||||
|
||||
"""
|
||||
@version: ??
|
||||
@author: liangliangyy
|
||||
@license: MIT Licence
|
||||
@contact: liangliangyy@gmail.com
|
||||
@site: https://www.lylinux.org/
|
||||
@software: PyCharm
|
||||
@file: forms.py
|
||||
@time: 2017/3/7 下午8:58
|
||||
"""
|
||||
|
||||
from django.contrib.auth.forms import forms
|
||||
from django.forms import widgets
|
||||
|
||||
|
||||
class RequireEmailForm(forms.Form):
|
||||
email = forms.EmailField(label='电子邮箱', required=True)
|
||||
oauthid = forms.IntegerField(widget=forms.HiddenInput, required=False)
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(RequireEmailForm, self).__init__(*args, **kwargs)
|
||||
self.fields['email'].widget = widgets.EmailInput(attrs={'placeholder': "email", "class": "form-control"})
|
||||
+11
-22
@@ -2,35 +2,24 @@ from django.db import models
|
||||
|
||||
# Create your models here.
|
||||
from django.conf import settings
|
||||
from django.utils.timezone import now
|
||||
|
||||
|
||||
class BaseModel(models.Model):
|
||||
author = models.ForeignKey(settings.AUTH_USER_MODEL, verbose_name='用户')
|
||||
class OAuthUser(models.Model):
|
||||
author = models.ForeignKey(settings.AUTH_USER_MODEL, verbose_name='用户', blank=True, null=True)
|
||||
openid = models.CharField(max_length=50)
|
||||
nikename = models.CharField(max_length=50, verbose_name='昵称')
|
||||
token = models.CharField(max_length=50)
|
||||
picture = models.CharField(max_length=50, blank=True, null=True)
|
||||
token = models.CharField(max_length=150, null=True, blank=True)
|
||||
picture = models.CharField(max_length=350, blank=True, null=True)
|
||||
type = models.CharField(blank=False, null=False, max_length=50)
|
||||
email = models.CharField(max_length=50, null=True, blank=True)
|
||||
matedata = models.CharField(max_length=2000, null=True, blank=True)
|
||||
created_time = models.DateTimeField('创建时间', default=now)
|
||||
last_mod_time = models.DateTimeField('修改时间', default=now)
|
||||
|
||||
def __str__(self):
|
||||
return self.nikename
|
||||
|
||||
class Meta:
|
||||
abstract = True
|
||||
|
||||
|
||||
class SinaWBUserInfo(BaseModel):
|
||||
class Meta:
|
||||
verbose_name = "新浪微博"
|
||||
verbose_name_plural = verbose_name
|
||||
|
||||
|
||||
class QQUserInfo(BaseModel):
|
||||
class Meta:
|
||||
verbose_name = "QQ"
|
||||
verbose_name_plural = verbose_name
|
||||
|
||||
|
||||
class GoogleUserInfo(BaseModel):
|
||||
class Meta:
|
||||
verbose_name = "Google"
|
||||
verbose_name = 'oauth用户'
|
||||
verbose_name_plural = verbose_name
|
||||
|
||||
+217
-35
@@ -14,36 +14,38 @@
|
||||
"""
|
||||
|
||||
from abc import ABCMeta, abstractmethod, abstractproperty
|
||||
from oauth.models import OAuthUser
|
||||
from django.conf import settings
|
||||
import requests
|
||||
import json
|
||||
import urllib.parse
|
||||
from DjangoBlog.utils import logger, parse_dict_to_url
|
||||
|
||||
|
||||
class BaseManager(metaclass=ABCMeta):
|
||||
class BaseOauthManager(metaclass=ABCMeta):
|
||||
"""获取用户授权"""
|
||||
AUTH_URL = None
|
||||
"""获取token"""
|
||||
TOKEN_URL = None
|
||||
"""获取用户信息"""
|
||||
API_URL = None
|
||||
'''icon图标名'''
|
||||
ICON_NAME = None
|
||||
|
||||
def __init__(self, client_id, client_secret, callback_url, access_token=None, openid=None):
|
||||
self.client_id = client_id
|
||||
self.client_secret = client_secret
|
||||
self.callback_url = callback_url
|
||||
def __init__(self, access_token=None, openid=None):
|
||||
self.access_token = access_token
|
||||
self.openid = openid
|
||||
|
||||
@property
|
||||
def is_access_token_set(self):
|
||||
return self.access_token != None
|
||||
return self.access_token is not None
|
||||
|
||||
@property
|
||||
def is_authorized(self):
|
||||
return self.is_access_token_set and self.access_token != None and self.openid != None
|
||||
return self.is_access_token_set and self.access_token is not None and self.openid is not None
|
||||
|
||||
@abstractmethod
|
||||
def get_authorization_url(self):
|
||||
def get_authorization_url(self, nexturl='/'):
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
@@ -63,26 +65,29 @@ class BaseManager(metaclass=ABCMeta):
|
||||
return rsp.text
|
||||
|
||||
|
||||
class WBOauthManager(BaseManager):
|
||||
class WBOauthManager(BaseOauthManager):
|
||||
AUTH_URL = 'https://api.weibo.com/oauth2/authorize'
|
||||
TOKEN_URL = 'https://api.weibo.com/oauth2/access_token'
|
||||
API_URL = 'https://api.weibo.com/2/users/show.json'
|
||||
ICON_NAME = 'weibo'
|
||||
|
||||
def __init__(self, client_id, client_secret, callback_url, access_token=None, openid=None):
|
||||
super(WBOauthManager, self).__init__(client_id=client_id, client_secret=client_secret,
|
||||
callback_url=callback_url, access_token=access_token, openid=openid)
|
||||
def __init__(self, access_token=None, openid=None):
|
||||
self.client_id = settings.OAHUTH['sina']['appkey']
|
||||
self.client_secret = settings.OAHUTH['sina']['appsecret']
|
||||
self.callback_url = settings.OAHUTH['sina']['callbackurl']
|
||||
super(WBOauthManager, self).__init__(access_token=access_token, openid=openid)
|
||||
|
||||
def get_authorization_url(self):
|
||||
def get_authorization_url(self, nexturl='/'):
|
||||
params = {
|
||||
'client_id': self.client_id,
|
||||
'response_type': 'code',
|
||||
'redirect_uri': self.callback_url
|
||||
'redirect_uri': self.callback_url + '&next_url=' + nexturl
|
||||
}
|
||||
url = self.AUTH_URL + "?" + urllib.parse.urlencode(params)
|
||||
return url
|
||||
|
||||
def get_access_token_by_code(self, code):
|
||||
print(code)
|
||||
|
||||
params = {
|
||||
'client_id': self.client_id,
|
||||
'client_secret': self.client_secret,
|
||||
@@ -91,15 +96,8 @@ class WBOauthManager(BaseManager):
|
||||
'redirect_uri': self.callback_url
|
||||
}
|
||||
rsp = self.do_post(self.TOKEN_URL, params)
|
||||
print(rsp)
|
||||
# return rsp
|
||||
|
||||
obj = json.loads(rsp)
|
||||
self.access_token = str(obj['access_token'])
|
||||
self.openid = str(obj['uid'])
|
||||
return self.get_oauth_userinfo()
|
||||
|
||||
try:
|
||||
obj = json.loads(rsp)
|
||||
self.access_token = str(obj['access_token'])
|
||||
self.openid = str(obj['uid'])
|
||||
return self.get_oauth_userinfo()
|
||||
@@ -114,26 +112,45 @@ class WBOauthManager(BaseManager):
|
||||
'access_token': self.access_token
|
||||
}
|
||||
rsp = self.do_get(self.API_URL, params)
|
||||
print(rsp)
|
||||
try:
|
||||
|
||||
datas = json.loads(rsp)
|
||||
user = OAuthUser()
|
||||
user.matedata = rsp
|
||||
user.picture = datas['avatar_large']
|
||||
user.nikename = datas['screen_name']
|
||||
user.openid = datas['id']
|
||||
user.type = 'weibo'
|
||||
user.token = self.access_token
|
||||
if 'email' in datas and datas['email']:
|
||||
user.email = datas['email']
|
||||
return user
|
||||
except:
|
||||
logger.info('weibo oauth error.rsp:' + rsp)
|
||||
return None
|
||||
|
||||
|
||||
class GoogleOauthManager(BaseManager):
|
||||
class GoogleOauthManager(BaseOauthManager):
|
||||
AUTH_URL = 'https://accounts.google.com/o/oauth2/v2/auth'
|
||||
TOKEN_URL = 'https://www.googleapis.com/oauth2/v4/token'
|
||||
API_URL = 'https://www.googleapis.com/oauth2/v3/userinfo'
|
||||
ICON_NAME = 'google'
|
||||
|
||||
def __init__(self, client_id, client_secret, callback_url, access_token=None, openid=None):
|
||||
super(GoogleOauthManager, self).__init__(client_id=client_id, client_secret=client_secret,
|
||||
callback_url=callback_url, access_token=access_token, openid=openid)
|
||||
def __init__(self, access_token=None, openid=None):
|
||||
self.client_id = settings.OAHUTH['google']['appkey']
|
||||
self.client_secret = settings.OAHUTH['google']['appsecret']
|
||||
self.callback_url = settings.OAHUTH['google']['callbackurl']
|
||||
super(GoogleOauthManager, self).__init__(access_token=access_token, openid=openid)
|
||||
|
||||
def get_authorization_url(self):
|
||||
def get_authorization_url(self, nexturl='/'):
|
||||
params = {
|
||||
'client_id': self.client_id,
|
||||
'response_type': 'code',
|
||||
'redirect_uri': self.callback_url,
|
||||
'scope': 'openid email',
|
||||
}
|
||||
url = self.AUTH_URL + "?" + urllib.parse.urlencode(params, quote_via=urllib.parse.quote)
|
||||
# url = self.AUTH_URL + "?" + urllib.parse.urlencode(params, quote_via=urllib.parse.quote)
|
||||
url = self.AUTH_URL + "?" + urllib.parse.urlencode(params)
|
||||
return url
|
||||
|
||||
def get_access_token_by_code(self, code):
|
||||
@@ -146,10 +163,16 @@ class GoogleOauthManager(BaseManager):
|
||||
'redirect_uri': self.callback_url
|
||||
}
|
||||
rsp = self.do_post(self.TOKEN_URL, params)
|
||||
print(rsp)
|
||||
|
||||
obj = json.loads(rsp)
|
||||
self.access_token = str(obj['access_token'])
|
||||
self.openid = str(obj['id_token'])
|
||||
try:
|
||||
self.access_token = str(obj['access_token'])
|
||||
self.openid = str(obj['id_token'])
|
||||
logger.info(self.ICON_NAME + ' oauth ' + rsp)
|
||||
return self.access_token
|
||||
except:
|
||||
logger.info(self.ICON_NAME + ' oauth error ' + rsp)
|
||||
return None
|
||||
|
||||
def get_oauth_userinfo(self):
|
||||
if not self.is_authorized:
|
||||
@@ -158,5 +181,164 @@ class GoogleOauthManager(BaseManager):
|
||||
'access_token': self.access_token
|
||||
}
|
||||
rsp = self.do_get(self.API_URL, params)
|
||||
print(rsp)
|
||||
return json.loads(rsp)
|
||||
try:
|
||||
|
||||
datas = json.loads(rsp)
|
||||
user = OAuthUser()
|
||||
user.matedata = rsp
|
||||
user.picture = datas['picture']
|
||||
user.nikename = datas['name']
|
||||
user.openid = datas['sub']
|
||||
user.token = self.access_token
|
||||
user.type = 'google'
|
||||
if datas['email']:
|
||||
user.email = datas['email']
|
||||
return user
|
||||
except:
|
||||
logger.info('google oauth error.rsp:' + rsp)
|
||||
return None
|
||||
|
||||
|
||||
class GitHubOauthManager(BaseOauthManager):
|
||||
AUTH_URL = 'https://github.com/login/oauth/authorize'
|
||||
TOKEN_URL = 'https://github.com/login/oauth/access_token'
|
||||
API_URL = 'https://api.github.com/user'
|
||||
ICON_NAME = 'github'
|
||||
|
||||
def __init__(self, access_token=None, openid=None):
|
||||
self.client_id = settings.OAHUTH['github']['appkey']
|
||||
self.client_secret = settings.OAHUTH['github']['appsecret']
|
||||
self.callback_url = settings.OAHUTH['github']['callbackurl']
|
||||
super(GitHubOauthManager, self).__init__(access_token=access_token, openid=openid)
|
||||
|
||||
def get_authorization_url(self, nexturl='/'):
|
||||
params = {
|
||||
'client_id': self.client_id,
|
||||
'response_type': 'code',
|
||||
'redirect_uri': self.callback_url + '&next_url=' + nexturl,
|
||||
'scope': 'user'
|
||||
}
|
||||
# url = self.AUTH_URL + "?" + urllib.parse.urlencode(params, quote_via=urllib.parse.quote)
|
||||
url = self.AUTH_URL + "?" + urllib.parse.urlencode(params)
|
||||
return url
|
||||
|
||||
def get_access_token_by_code(self, code):
|
||||
params = {
|
||||
'client_id': self.client_id,
|
||||
'client_secret': self.client_secret,
|
||||
'grant_type': 'authorization_code',
|
||||
'code': code,
|
||||
|
||||
'redirect_uri': self.callback_url
|
||||
}
|
||||
rsp = self.do_post(self.TOKEN_URL, params)
|
||||
|
||||
try:
|
||||
from urllib import parse
|
||||
r = parse.parse_qs(rsp)
|
||||
self.access_token = (r['access_token'][0])
|
||||
return self.access_token
|
||||
except:
|
||||
return None
|
||||
|
||||
def get_oauth_userinfo(self):
|
||||
|
||||
params = {
|
||||
'access_token': self.access_token
|
||||
}
|
||||
rsp = self.do_get(self.API_URL, params)
|
||||
|
||||
try:
|
||||
datas = json.loads(rsp)
|
||||
user = OAuthUser()
|
||||
user.picture = datas['avatar_url']
|
||||
user.nikename = datas['name']
|
||||
user.openid = datas['id']
|
||||
user.type = 'github'
|
||||
user.token = self.access_token
|
||||
user.matedata = rsp
|
||||
if datas['email']:
|
||||
user.email = datas['email']
|
||||
|
||||
return user
|
||||
except:
|
||||
logger.info('github oauth error.rsp:' + rsp)
|
||||
return None
|
||||
|
||||
|
||||
class FaceBookOauthManager(BaseOauthManager):
|
||||
AUTH_URL = 'https://www.facebook.com/v2.10/dialog/oauth'
|
||||
TOKEN_URL = 'https://graph.facebook.com/v2.10/oauth/access_token'
|
||||
API_URL = 'https://graph.facebook.com/me'
|
||||
ICON_NAME = 'facebook'
|
||||
|
||||
def __init__(self, access_token=None, openid=None):
|
||||
self.client_id = settings.OAHUTH['facebook']['appkey']
|
||||
self.client_secret = settings.OAHUTH['facebook']['appsecret']
|
||||
self.callback_url = settings.OAHUTH['facebook']['callbackurl']
|
||||
super(FaceBookOauthManager, self).__init__(access_token=access_token, openid=openid)
|
||||
|
||||
def get_authorization_url(self, nexturl='/'):
|
||||
params = {
|
||||
'client_id': self.client_id,
|
||||
'response_type': 'code',
|
||||
'redirect_uri': self.callback_url, # + '&next_url=' + nexturl,
|
||||
'scope': 'email,public_profile'
|
||||
}
|
||||
url = self.AUTH_URL + "?" + urllib.parse.urlencode(params)
|
||||
return url
|
||||
|
||||
def get_access_token_by_code(self, code):
|
||||
params = {
|
||||
'client_id': self.client_id,
|
||||
'client_secret': self.client_secret,
|
||||
# 'grant_type': 'authorization_code',
|
||||
'code': code,
|
||||
|
||||
'redirect_uri': self.callback_url
|
||||
}
|
||||
rsp = self.do_post(self.TOKEN_URL, params)
|
||||
|
||||
try:
|
||||
obj = json.loads(rsp)
|
||||
token = str(obj['access_token'])
|
||||
self.access_token = token
|
||||
return self.access_token
|
||||
except:
|
||||
return None
|
||||
|
||||
def get_oauth_userinfo(self):
|
||||
params = {
|
||||
'access_token': self.access_token,
|
||||
'fields': 'id,name,picture,email'
|
||||
}
|
||||
try:
|
||||
rsp = self.do_get(self.API_URL, params)
|
||||
datas = json.loads(rsp)
|
||||
user = OAuthUser()
|
||||
user.nikename = datas['name']
|
||||
user.openid = datas['id']
|
||||
user.type = 'facebook'
|
||||
user.token = self.access_token
|
||||
user.matedata = rsp
|
||||
if datas['email']:
|
||||
user.email = datas['email']
|
||||
if datas['picture'] and datas['picture']['data'] and datas['picture']['data']['url']:
|
||||
user.picture = str(datas['picture']['data']['url'])
|
||||
return user
|
||||
except Exception as e:
|
||||
logger.warn(e)
|
||||
return None
|
||||
|
||||
|
||||
def get_oauth_apps():
|
||||
applications = BaseOauthManager.__subclasses__()
|
||||
return list(map(lambda x: x(), applications))
|
||||
|
||||
|
||||
def get_manager_by_type(type):
|
||||
applications = get_oauth_apps()
|
||||
finds = list(filter(lambda x: x.ICON_NAME.lower() == type.lower(), applications))
|
||||
if finds:
|
||||
return finds[0]
|
||||
return None
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
#!/usr/bin/env python
|
||||
# encoding: utf-8
|
||||
|
||||
|
||||
"""
|
||||
@version: ??
|
||||
@author: liangliangyy
|
||||
@license: MIT Licence
|
||||
@contact: liangliangyy@gmail.com
|
||||
@site: https://www.lylinux.org/
|
||||
@software: PyCharm
|
||||
@file: __init__.py
|
||||
@time: 2017/3/4 下午3:22
|
||||
"""
|
||||
@@ -0,0 +1,34 @@
|
||||
#!/usr/bin/env python
|
||||
# encoding: utf-8
|
||||
|
||||
|
||||
"""
|
||||
@version: ??
|
||||
@author: liangliangyy
|
||||
@license: MIT Licence
|
||||
@contact: liangliangyy@gmail.com
|
||||
@site: https://www.lylinux.org/
|
||||
@software: PyCharm
|
||||
@file: oauth_tags.py
|
||||
@time: 2017/3/4 下午3:22
|
||||
"""
|
||||
from oauth.oauthmanager import get_oauth_apps
|
||||
from django.core.urlresolvers import reverse
|
||||
from django import template
|
||||
from django.conf import settings
|
||||
|
||||
register = template.Library()
|
||||
|
||||
|
||||
@register.inclusion_tag('oauth/oauth_applications.html')
|
||||
def load_oauth_applications(request):
|
||||
applications = get_oauth_apps()
|
||||
baseurl = reverse('oauth:oauthlogin')
|
||||
path = request.get_full_path()
|
||||
|
||||
apps = list(map(lambda x: (x.ICON_NAME,
|
||||
'{baseurl}?type={type}&next_url={next}'
|
||||
.format(baseurl=baseurl, type=x.ICON_NAME, next=path)), applications))
|
||||
return {
|
||||
'apps': apps
|
||||
}
|
||||
@@ -1,13 +1,3 @@
|
||||
from django.test import TestCase
|
||||
|
||||
# Create your tests here.
|
||||
|
||||
class OAuthTet(TestCase):
|
||||
def setUp(self):
|
||||
pass
|
||||
|
||||
|
||||
from oauth.oauthmanager import WBOauthManager
|
||||
from django.conf import settings
|
||||
settings.OAHUTH['sina']
|
||||
manager=WBOauthManager(client_id=settings.OAHUTH['sina']['appkey'],client_secret=settings.OAHUTH['sina']['appsecret'],callback_url=settings.OAHUTH['sina']['callbackurl'])
|
||||
+5
-5
@@ -18,9 +18,9 @@ from django.views.decorators.cache import cache_page
|
||||
from . import views
|
||||
|
||||
urlpatterns = [
|
||||
url(r'^oauth/wbauthorize/(?P<sitename>\w+)$', views.wbauthorize),
|
||||
url(r'^oauth/wboauthurl$', views.wboauthurl),
|
||||
# url(r'^oauth/wbauthorize/(?P<sitename>\w+)$', views.wbauthorize),
|
||||
url(r'^oauth/googleoauthurl', views.googleoauthurl),
|
||||
url(r'^oauth/googleauthorize', views.googleauthorize),
|
||||
url(r'^oauth/authorize$', views.authorize),
|
||||
url(r'^oauth/requireemail/(?P<oauthid>\d+).html', views.RequireEmailView.as_view(), name='require_email'),
|
||||
url(r'^oauth/emailconfirm/(?P<id>\d+)/(?P<sign>\S+).html', views.emailconfirm, name='email_confirm'),
|
||||
url(r'^oauth/bindsuccess/(?P<oauthid>\d+).html', views.bindsuccess, name='bindsuccess'),
|
||||
url(r'^oauth/oauthlogin$', views.oauthlogin,name='oauthlogin')
|
||||
]
|
||||
|
||||
+201
-48
@@ -1,59 +1,212 @@
|
||||
from django.shortcuts import render
|
||||
|
||||
# Create your views here.
|
||||
from .oauthmanager import WBOauthManager, GoogleOauthManager
|
||||
|
||||
from django.conf import settings
|
||||
from django.http import HttpResponse
|
||||
from django.http import HttpResponse, HttpResponseRedirect
|
||||
from django.contrib.auth import get_user_model
|
||||
from .models import GoogleUserInfo
|
||||
from .models import OAuthUser
|
||||
from django.contrib.auth import login
|
||||
from django.shortcuts import get_object_or_404
|
||||
from django.views.generic import FormView, RedirectView
|
||||
from oauth.forms import RequireEmailForm
|
||||
from django.core.urlresolvers import reverse
|
||||
from DjangoBlog.utils import send_email, get_md5
|
||||
from django.contrib.sites.models import Site
|
||||
from django.core.exceptions import ObjectDoesNotExist
|
||||
from django.http import HttpResponseForbidden
|
||||
from .oauthmanager import get_manager_by_type
|
||||
|
||||
|
||||
def wbauthorize(request, sitename):
|
||||
manager = WBOauthManager(client_id=settings.OAHUTH['sina']['appkey'],
|
||||
client_secret=settings.OAHUTH['sina']['appsecret'],
|
||||
callback_url=settings.OAHUTH['sina']['callbackurl'])
|
||||
def oauthlogin(request):
|
||||
type = request.GET.get('type', None)
|
||||
if not type:
|
||||
return HttpResponseRedirect('/')
|
||||
manager = get_manager_by_type(type)
|
||||
if not manager:
|
||||
return HttpResponseRedirect('/')
|
||||
nexturl = request.GET.get('next_url', None)
|
||||
if not nexturl or nexturl == '/login/':
|
||||
nexturl = '/'
|
||||
authorizeurl = manager.get_authorization_url(nexturl)
|
||||
return HttpResponseRedirect(authorizeurl)
|
||||
|
||||
|
||||
def authorize(request):
|
||||
manager = None
|
||||
type = request.GET.get('type', None)
|
||||
if not type:
|
||||
return HttpResponseRedirect('/')
|
||||
manager = get_manager_by_type(type)
|
||||
if not manager:
|
||||
return HttpResponseRedirect('/')
|
||||
code = request.GET.get('code', None)
|
||||
rsp = manager.get_access_token_by_code(code)
|
||||
print(rsp)
|
||||
return HttpResponse(rsp)
|
||||
|
||||
|
||||
def wboauthurl(request):
|
||||
manager = WBOauthManager(client_id=settings.OAHUTH['sina']['appkey'],
|
||||
client_secret=settings.OAHUTH['sina']['appsecret'],
|
||||
callback_url=settings.OAHUTH['sina']['callbackurl'])
|
||||
url = manager.get_authorization_url()
|
||||
return HttpResponse(url)
|
||||
|
||||
|
||||
def googleoauthurl(request):
|
||||
manager = GoogleOauthManager(client_id=settings.OAHUTH['google']['appkey'],
|
||||
client_secret=settings.OAHUTH['google']['appsecret'],
|
||||
callback_url=settings.OAHUTH['google']['callbackurl'])
|
||||
url = manager.get_authorization_url()
|
||||
return HttpResponse(url)
|
||||
|
||||
|
||||
def googleauthorize(request):
|
||||
manager = GoogleOauthManager(client_id=settings.OAHUTH['google']['appkey'],
|
||||
client_secret=settings.OAHUTH['google']['appsecret'],
|
||||
callback_url=settings.OAHUTH['google']['callbackurl'])
|
||||
code = request.GET.get('code', None)
|
||||
rsp = manager.get_access_token_by_code(code)
|
||||
print(rsp)
|
||||
nexturl = request.GET.get('next_url', None)
|
||||
if not nexturl:
|
||||
nexturl = '/'
|
||||
if not rsp:
|
||||
return HttpResponseRedirect(manager.get_authorization_url(nexturl))
|
||||
user = manager.get_oauth_userinfo()
|
||||
|
||||
if user:
|
||||
email = user['email']
|
||||
author = get_user_model().objects.get(email=email)
|
||||
if not author:
|
||||
author = get_user_model().objects.create_user(username=user["name"], email=email, password=None,
|
||||
nikename=user["name"])
|
||||
if not GoogleUserInfo.objects.filter(author_id=author.pk):
|
||||
userinfo = GoogleUserInfo()
|
||||
userinfo.author = author
|
||||
userinfo.picture = user["picture"]
|
||||
userinfo.token = manager.access_token
|
||||
userinfo.openid = manager.openid
|
||||
userinfo.nikename = user["name"]
|
||||
userinfo.save()
|
||||
return HttpResponse(rsp)
|
||||
if not user.nikename:
|
||||
import datetime
|
||||
user.nikename = "djangoblog" + datetime.datetime.now().strftime('%y%m%d%I%M%S')
|
||||
try:
|
||||
token = user.token
|
||||
user = OAuthUser.objects.get(type=type, openid=user.openid)
|
||||
if token:
|
||||
user.token = token
|
||||
except ObjectDoesNotExist:
|
||||
pass
|
||||
email = user.email
|
||||
if email:
|
||||
author = None
|
||||
try:
|
||||
author = get_user_model().objects.get(id=user.author_id)
|
||||
except ObjectDoesNotExist:
|
||||
pass
|
||||
if not author:
|
||||
result = get_user_model().objects.get_or_create(email=user.email)
|
||||
author = result[0]
|
||||
if result[1]:
|
||||
author.username = user.nikename
|
||||
author.save()
|
||||
|
||||
user.author = author
|
||||
user.save()
|
||||
login(request, author)
|
||||
return HttpResponseRedirect(nexturl)
|
||||
if not email:
|
||||
user.save()
|
||||
url = reverse('oauth:require_email', kwargs={
|
||||
'oauthid': user.id
|
||||
})
|
||||
|
||||
return HttpResponseRedirect(url)
|
||||
else:
|
||||
return HttpResponseRedirect(nexturl)
|
||||
|
||||
|
||||
def emailconfirm(request, id, sign):
|
||||
if not sign:
|
||||
return HttpResponseForbidden()
|
||||
if not get_md5(settings.SECRET_KEY + str(id) + settings.SECRET_KEY).upper() == sign.upper():
|
||||
return HttpResponseForbidden()
|
||||
oauthuser = get_object_or_404(OAuthUser, pk=id)
|
||||
author = None
|
||||
if oauthuser.author:
|
||||
author = get_user_model().objects.get(pk=oauthuser.author_id)
|
||||
else:
|
||||
result = get_user_model().objects.get_or_create(email=oauthuser.email)
|
||||
author = result[0]
|
||||
if result[1]:
|
||||
author.username = oauthuser.nikename
|
||||
author.save()
|
||||
"""
|
||||
if oauthuser.email and author.email:
|
||||
login(request, author)
|
||||
return HttpResponseRedirect('/')
|
||||
"""
|
||||
oauthuser.author = author
|
||||
oauthuser.save()
|
||||
login(request, author)
|
||||
|
||||
site = Site.objects.get_current().domain
|
||||
content = '''
|
||||
<p>恭喜您,您已经成功绑定您的邮箱,您可以使用{type}来直接免密码登录本网站.欢迎您继续关注本站,地址是</p>
|
||||
|
||||
<a href="{url}" rel="bookmark">{url}</a>
|
||||
|
||||
再次感谢您!
|
||||
<br />
|
||||
如果上面链接无法打开,请将此链接复制至浏览器。
|
||||
{url}
|
||||
'''.format(type=oauthuser.type, url='http://' + site)
|
||||
|
||||
send_email(emailto=[oauthuser.email, ], title='恭喜您绑定成功!', content=content)
|
||||
url = reverse('oauth:bindsuccess', kwargs={
|
||||
'oauthid': id
|
||||
})
|
||||
url = url + '?type=success'
|
||||
return HttpResponseRedirect(url)
|
||||
|
||||
|
||||
class RequireEmailView(FormView):
|
||||
form_class = RequireEmailForm
|
||||
template_name = 'oauth/require_email.html'
|
||||
|
||||
def get(self, request, *args, **kwargs):
|
||||
oauthid = self.kwargs['oauthid']
|
||||
oauthuser = get_object_or_404(OAuthUser, pk=oauthid)
|
||||
if oauthuser.email:
|
||||
pass
|
||||
# return HttpResponseRedirect('/')
|
||||
|
||||
return super(RequireEmailView, self).get(request, *args, **kwargs)
|
||||
|
||||
def get_initial(self):
|
||||
oauthid = self.kwargs['oauthid']
|
||||
return {
|
||||
'email': '',
|
||||
'oauthid': oauthid
|
||||
}
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
oauthid = self.kwargs['oauthid']
|
||||
oauthuser = get_object_or_404(OAuthUser, pk=oauthid)
|
||||
if oauthuser.picture:
|
||||
kwargs['picture'] = oauthuser.picture
|
||||
return super(RequireEmailView, self).get_context_data(**kwargs)
|
||||
|
||||
def form_valid(self, form):
|
||||
email = form.cleaned_data['email']
|
||||
oauthid = form.cleaned_data['oauthid']
|
||||
oauthuser = get_object_or_404(OAuthUser, pk=oauthid)
|
||||
oauthuser.email = email
|
||||
oauthuser.save()
|
||||
sign = get_md5(settings.SECRET_KEY + str(oauthuser.id) + settings.SECRET_KEY)
|
||||
site = Site.objects.get_current().domain
|
||||
if settings.DEBUG:
|
||||
site = '127.0.0.1:8000'
|
||||
path = reverse('oauth:email_confirm', kwargs={
|
||||
'id': oauthid,
|
||||
'sign': sign
|
||||
})
|
||||
url = "http://{site}{path}".format(site=site, path=path)
|
||||
|
||||
content = """
|
||||
<p>请点击下面链接绑定您的邮箱</p>
|
||||
|
||||
<a href="{url}" rel="bookmark">{url}</a>
|
||||
|
||||
再次感谢您!
|
||||
<br />
|
||||
如果上面链接无法打开,请将此链接复制至浏览器。
|
||||
{url}
|
||||
""".format(url=url)
|
||||
send_email(emailto=[email, ], title='绑定您的电子邮箱', content=content)
|
||||
url = reverse('oauth:bindsuccess', kwargs={
|
||||
'oauthid': oauthid
|
||||
})
|
||||
url = url + '?type=email'
|
||||
return HttpResponseRedirect(url)
|
||||
|
||||
|
||||
def bindsuccess(request, oauthid):
|
||||
type = request.GET.get('type', None)
|
||||
|
||||
title = ''
|
||||
content = ''
|
||||
oauthuser = get_object_or_404(OAuthUser, pk=oauthid)
|
||||
if type == 'email':
|
||||
title = '绑定成功'
|
||||
content = "恭喜您,还差一步就绑定成功了,请登录您的邮箱查看邮件完成绑定,谢谢。"
|
||||
else:
|
||||
title = '绑定成功'
|
||||
content = "恭喜您绑定成功,您以后可以使用{type}来直接免密码登录本站啦,感谢您对本站对关注。".format(type=oauthuser.type)
|
||||
return render(request, 'oauth/bindsuccess.html', {
|
||||
'title': title,
|
||||
'content': content
|
||||
})
|
||||
|
||||
+26
-18
@@ -1,26 +1,34 @@
|
||||
Django==1.10.5
|
||||
appdirs==1.4.3
|
||||
coverage==4.4.1
|
||||
Django==1.11.7
|
||||
django-appconf==1.0.2
|
||||
django-autoslug==1.9.3
|
||||
django-compressor==2.1.1
|
||||
django-debug-toolbar==1.6
|
||||
django-haystack==2.6.0
|
||||
django-compressor==2.2
|
||||
django-debug-toolbar==1.9.1
|
||||
django-haystack==2.6.1
|
||||
django-ipware==1.1.6
|
||||
django-pagedown==0.1.1
|
||||
django-pagedown==0.1.3
|
||||
django-uuslug==1.1.8
|
||||
jieba==0.38
|
||||
markdown2==2.3.2
|
||||
mistune==0.7.3
|
||||
Pillow==4.0.0
|
||||
Pygments==2.1.3
|
||||
PyMySQL==0.7.9
|
||||
jieba==0.39
|
||||
jsonpickle==0.9.5
|
||||
markdown2==2.3.5
|
||||
mistune==0.8.1
|
||||
olefile==0.44
|
||||
packaging==16.8
|
||||
Pillow==4.3.0
|
||||
Pygments==2.2.0
|
||||
PyMySQL==0.7.11
|
||||
pyparsing==2.2.0
|
||||
python-memcached==1.58
|
||||
python-slugify==1.2.1
|
||||
pytz==2016.10
|
||||
python-slugify==1.2.4
|
||||
pytz==2017.3
|
||||
rcssmin==1.0.6
|
||||
requests==2.12.4
|
||||
requests==2.18.4
|
||||
rjsmin==1.0.12
|
||||
six==1.10.0
|
||||
sqlparse==0.2.2
|
||||
Unidecode==0.4.20
|
||||
webencodings==0.5
|
||||
six==1.11.0
|
||||
sqlparse==0.2.4
|
||||
Unidecode==0.4.21
|
||||
urllib3==1.22
|
||||
webencodings==0.5.1
|
||||
WeRoBot==1.1.1
|
||||
Whoosh==2.7.4
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
User-agent: *
|
||||
Disallow: /admin/
|
||||
Disallow: /static/
|
||||
@@ -0,0 +1,14 @@
|
||||
#!/usr/bin/env python
|
||||
# encoding: utf-8
|
||||
|
||||
|
||||
"""
|
||||
@version: ??
|
||||
@author: liangliangyy
|
||||
@license: MIT Licence
|
||||
@contact: liangliangyy@gmail.com
|
||||
@site: https://www.lylinux.org/
|
||||
@software: PyCharm
|
||||
@file: __init__.py.py
|
||||
@time: 2017/8/27 上午11:40
|
||||
"""
|
||||
@@ -0,0 +1,40 @@
|
||||
#!/usr/bin/env python
|
||||
# encoding: utf-8
|
||||
|
||||
|
||||
"""
|
||||
@version: ??
|
||||
@author: liangliangyy
|
||||
@license: MIT Licence
|
||||
@contact: liangliangyy@gmail.com
|
||||
@site: https://www.lylinux.org/
|
||||
@software: PyCharm
|
||||
@file: blogapi.py
|
||||
@time: 2017/8/27 上午11:40
|
||||
"""
|
||||
from blog.models import Article, Category, Tag
|
||||
from haystack.query import EmptySearchQuerySet, SearchQuerySet
|
||||
|
||||
|
||||
class BlogApi():
|
||||
def __init__(self):
|
||||
self.searchqueryset = SearchQuerySet()
|
||||
self.searchqueryset.auto_query('')
|
||||
self.__max_takecount__ = 8
|
||||
|
||||
def search_articles(self, query):
|
||||
sqs = self.searchqueryset.auto_query(query)
|
||||
sqs = sqs.load_all()
|
||||
return sqs[:self.__max_takecount__]
|
||||
|
||||
def get_category_lists(self):
|
||||
return Category.objects.all()
|
||||
|
||||
def get_category_articles(self, categoryname):
|
||||
articles = Article.objects.filter(category__name=categoryname)
|
||||
if articles:
|
||||
return articles[:self.__max_takecount__]
|
||||
return None
|
||||
|
||||
def get_recent_articles(self):
|
||||
return Article.objects.all()[:self.__max_takecount__]
|
||||
@@ -0,0 +1,43 @@
|
||||
#!/usr/bin/env python
|
||||
# encoding: utf-8
|
||||
|
||||
|
||||
"""
|
||||
@version: ??
|
||||
@author: liangliangyy
|
||||
@license: MIT Licence
|
||||
@contact: liangliangyy@gmail.com
|
||||
@site: https://www.lylinux.org/
|
||||
@software: PyCharm
|
||||
@file: commonapi.py
|
||||
@time: 2017/9/2 上午1:43
|
||||
"""
|
||||
import requests
|
||||
import json
|
||||
from DjangoBlog.utils import logger
|
||||
|
||||
|
||||
class TuLing():
|
||||
def __init__(self):
|
||||
self.__key__ = '2f1446eb0321804291b0a1e217c25bb5'
|
||||
self.__appid__ = 137762
|
||||
|
||||
def __build_req_url(self, content):
|
||||
return 'http://www.tuling123.com/openapi/api?key=%s&info=%s&userid=%s' % (
|
||||
self.__key__, content, self.__appid__)
|
||||
|
||||
def UserAgent(self, url):
|
||||
rsp = requests.get(url)
|
||||
return rsp.content
|
||||
|
||||
def getdata(self, content):
|
||||
try:
|
||||
requrl = self.__build_req_url(content)
|
||||
res = self.UserAgent(requrl).decode('utf-8')
|
||||
|
||||
jsons = json.loads(res, encoding='utf-8')
|
||||
if str(jsons["code"]) == '100000':
|
||||
return jsons["text"]
|
||||
except Exception as e:
|
||||
logger.warn(e)
|
||||
return "哎呀,出错啦。"
|
||||
@@ -0,0 +1,45 @@
|
||||
#!/usr/bin/env python
|
||||
# encoding: utf-8
|
||||
|
||||
|
||||
"""
|
||||
@version: ??
|
||||
@author: liangliangyy
|
||||
@license: MIT Licence
|
||||
@contact: liangliangyy@gmail.com
|
||||
@site: https://www.lylinux.org/
|
||||
@software: PyCharm
|
||||
@file: MemcacheStorage.py
|
||||
@time: 2017/8/27 上午2:42
|
||||
"""
|
||||
from werobot.session import SessionStorage
|
||||
from werobot.utils import json_loads, json_dumps
|
||||
from DjangoBlog.utils import cache
|
||||
|
||||
|
||||
class MemcacheStorage(SessionStorage):
|
||||
def __init__(self, prefix='ws_'):
|
||||
self.prefix = prefix
|
||||
self.cache = cache
|
||||
|
||||
@property
|
||||
def is_available(self):
|
||||
value = "1"
|
||||
self.set('checkavaliable', value=value)
|
||||
return value == self.get('checkavaliable')
|
||||
|
||||
def key_name(self, s):
|
||||
return '{prefix}{s}'.format(prefix=self.prefix, s=s)
|
||||
|
||||
def get(self, id):
|
||||
id = self.key_name(id)
|
||||
session_json = self.cache.get(id) or '{}'
|
||||
return json_loads(session_json)
|
||||
|
||||
def set(self, id, value):
|
||||
id = self.key_name(id)
|
||||
self.cache.set(id, json_dumps(value))
|
||||
|
||||
def delete(self, id):
|
||||
id = self.key_name(id)
|
||||
self.cache.delete(id)
|
||||
@@ -0,0 +1,11 @@
|
||||
from django.contrib import admin
|
||||
|
||||
# Register your models here.
|
||||
from .models import commands
|
||||
|
||||
|
||||
class CommandsAdmin(admin.ModelAdmin):
|
||||
list_display = ('title', 'command', 'describe')
|
||||
|
||||
|
||||
admin.site.register(commands, CommandsAdmin)
|
||||
@@ -0,0 +1,5 @@
|
||||
from django.apps import AppConfig
|
||||
|
||||
|
||||
class ServermanagerConfig(AppConfig):
|
||||
name = 'servermanager'
|
||||
@@ -0,0 +1,17 @@
|
||||
from django.db import models
|
||||
|
||||
|
||||
# Create your models here.
|
||||
class commands(models.Model):
|
||||
title = models.CharField('命令标题', max_length=300)
|
||||
command = models.CharField('命令', max_length=2000)
|
||||
describe = models.CharField('命令描述', max_length=300)
|
||||
created_time = models.DateTimeField('创建时间', auto_now_add=True)
|
||||
last_mod_time = models.DateTimeField('修改时间', auto_now=True)
|
||||
|
||||
def __str__(self):
|
||||
return self.title
|
||||
|
||||
class Meta:
|
||||
verbose_name = '命令'
|
||||
verbose_name_plural = verbose_name
|
||||
@@ -0,0 +1,245 @@
|
||||
#!/usr/bin/env python
|
||||
# encoding: utf-8
|
||||
|
||||
|
||||
"""
|
||||
@version: ??
|
||||
@author: liangliangyy
|
||||
@license: MIT Licence
|
||||
@contact: liangliangyy@gmail.com
|
||||
@site: https://www.lylinux.org/
|
||||
@software: PyCharm
|
||||
@file: robot.py
|
||||
@time: 2017/8/27 上午1:55
|
||||
"""
|
||||
|
||||
from werobot import WeRoBot
|
||||
import re
|
||||
from werobot.replies import ArticlesReply, MusicReply, ImageReply, Article
|
||||
from .MemcacheStorage import MemcacheStorage
|
||||
from servermanager.Api.blogapi import BlogApi
|
||||
from servermanager.Api.commonapi import TuLing
|
||||
import os
|
||||
import json
|
||||
from DjangoBlog.utils import get_md5
|
||||
from django.conf import settings
|
||||
import jsonpickle
|
||||
from servermanager.models import commands
|
||||
|
||||
robot = WeRoBot(token='lylinux', enable_session=True)
|
||||
memstorage = MemcacheStorage()
|
||||
if memstorage.is_available:
|
||||
robot.config['SESSION_STORAGE'] = memstorage
|
||||
else:
|
||||
from werobot.session.filestorage import FileStorage
|
||||
import os
|
||||
from django.conf import settings
|
||||
|
||||
if os.path.exists(os.path.join(settings.BASE_DIR, 'werobot_session')):
|
||||
os.remove(os.path.join(settings.BASE_DIR, 'werobot_session'))
|
||||
robot.config['SESSION_STORAGE'] = FileStorage(filename='werobot_session')
|
||||
blogapi = BlogApi()
|
||||
tuling = TuLing()
|
||||
|
||||
|
||||
def convert_to_articlereply(articles, message):
|
||||
reply = ArticlesReply(message=message)
|
||||
from blog.templatetags.blog_tags import custom_markdown, truncatechars_content
|
||||
from DjangoBlog.utils import CommonMarkdown
|
||||
from django.utils.safestring import mark_safe
|
||||
for post in articles:
|
||||
imgs = re.findall(r'(?:http\:|https\:)?\/\/.*\.(?:png|jpg)', post.body)
|
||||
imgurl = ''
|
||||
if imgs:
|
||||
imgurl = imgs[0]
|
||||
article = Article(
|
||||
title=post.title,
|
||||
description=truncatechars_content(post.body),
|
||||
img=imgurl,
|
||||
url=post.get_full_url()
|
||||
)
|
||||
reply.add_article(article)
|
||||
return reply
|
||||
|
||||
|
||||
@robot.filter(re.compile(r"^\?.*"))
|
||||
def search(message, session):
|
||||
s = message.content
|
||||
searchstr = str(s).replace('?', '')
|
||||
result = blogapi.search_articles(searchstr)
|
||||
if result:
|
||||
articles = list(map(lambda x: x.object, result))
|
||||
reply = convert_to_articlereply(articles, message)
|
||||
return reply
|
||||
else:
|
||||
return '没有找到相关文章。'
|
||||
|
||||
|
||||
@robot.filter(re.compile(r'^category\s*$', re.I))
|
||||
def category(message, session):
|
||||
categorys = blogapi.get_category_lists()
|
||||
content = ','.join(map(lambda x: x.name, categorys))
|
||||
return '所有文章分类目录:' + content
|
||||
|
||||
|
||||
@robot.filter(re.compile(r'^recent\s*$', re.I))
|
||||
def recents(message, session):
|
||||
articles = blogapi.get_recent_articles()
|
||||
if articles:
|
||||
reply = convert_to_articlereply(articles, message)
|
||||
return reply
|
||||
else:
|
||||
return "暂时还没有文章"
|
||||
|
||||
|
||||
@robot.filter(re.compile('^help$', re.I))
|
||||
def help(message, session):
|
||||
return '''欢迎关注!
|
||||
默认会与图灵机器人聊天~~
|
||||
你可以通过下面这些命令来获得信息
|
||||
?关键字搜索文章.
|
||||
如?python.
|
||||
category获得文章分类目录及文章数.
|
||||
category-***获得该分类目录文章
|
||||
如category-python
|
||||
recent获得最新文章
|
||||
help获得帮助.
|
||||
weather:获得天气
|
||||
如weather:西安
|
||||
idcard:获得身份证信息
|
||||
如idcard:61048119xxxxxxxxxx
|
||||
music:音乐搜索
|
||||
如music:阴天快乐
|
||||
PS:以上标点符号都不支持中文标点~~
|
||||
'''
|
||||
|
||||
|
||||
@robot.filter(re.compile('^weather\:.*$', re.I))
|
||||
def weather(message, session):
|
||||
return "建设中..."
|
||||
|
||||
|
||||
@robot.filter(re.compile('^idcard\:.*$', re.I))
|
||||
def idcard(message, session):
|
||||
return "建设中..."
|
||||
|
||||
|
||||
@robot.handler
|
||||
def echo(message, session):
|
||||
handler = MessageHandler(message, session)
|
||||
return handler.handler()
|
||||
|
||||
|
||||
class CommandHandler():
|
||||
def __init__(self):
|
||||
self.commands = commands.objects.all()
|
||||
|
||||
def run(self, title):
|
||||
cmd = list(filter(lambda x: x.title.upper() == title.upper(), self.commands))
|
||||
if cmd:
|
||||
return self.__run_command__(cmd[0].command)
|
||||
else:
|
||||
return "未找到相关命令,请输入hepme获得帮助。"
|
||||
|
||||
def __run_command__(self, cmd):
|
||||
try:
|
||||
str = os.popen(cmd).read()
|
||||
return str
|
||||
except:
|
||||
return '命令执行出错!'
|
||||
|
||||
def get_help(self):
|
||||
rsp = ''
|
||||
for cmd in self.commands:
|
||||
rsp += '{c}:{d}\n'.format(c=cmd.title, d=cmd.describe)
|
||||
return rsp
|
||||
|
||||
|
||||
cmdhandler = CommandHandler()
|
||||
|
||||
|
||||
class MessageHandler():
|
||||
def __init__(self, message, session):
|
||||
userid = message.source
|
||||
self.message = message
|
||||
self.session = session
|
||||
self.userid = userid
|
||||
try:
|
||||
info = session[userid]
|
||||
self.userinfo = jsonpickle.decode(info)
|
||||
except:
|
||||
userinfo = WxUserInfo()
|
||||
self.userinfo = userinfo
|
||||
|
||||
@property
|
||||
def is_admin(self):
|
||||
return self.userinfo.isAdmin
|
||||
|
||||
@property
|
||||
def is_password_set(self):
|
||||
return self.userinfo.isPasswordSet
|
||||
|
||||
def savesession(self):
|
||||
info = jsonpickle.encode(self.userinfo)
|
||||
self.session[self.userid] = info
|
||||
|
||||
def handler(self):
|
||||
info = self.message.content
|
||||
|
||||
if self.userinfo.isAdmin and info.upper() == 'EXIT':
|
||||
self.userinfo = WxUserInfo()
|
||||
self.savesession()
|
||||
return "退出成功"
|
||||
if info.upper() == 'ADMIN':
|
||||
self.userinfo.isAdmin = True
|
||||
self.savesession()
|
||||
return "输入管理员密码"
|
||||
if self.userinfo.isAdmin and not self.userinfo.isPasswordSet:
|
||||
passwd = settings.WXADMIN
|
||||
if settings.TESTING:
|
||||
passwd='123'
|
||||
if passwd.upper() == get_md5(get_md5(info)).upper():
|
||||
self.userinfo.isPasswordSet = True
|
||||
self.savesession()
|
||||
return "验证通过,请输入命令或者要执行的命令代码:输入helpme获得帮助"
|
||||
else:
|
||||
if self.userinfo.Count >= 3:
|
||||
self.userinfo = WxUserInfo()
|
||||
self.savesession()
|
||||
return "超过验证次数"
|
||||
self.userinfo.Count += 1
|
||||
self.savesession()
|
||||
return "验证失败,请重新输入管理员密码:"
|
||||
if self.userinfo.isAdmin and self.userinfo.isPasswordSet:
|
||||
if self.userinfo.Command != '' and info.upper() == 'Y':
|
||||
return cmdhandler.run(self.userinfo.Command)
|
||||
else:
|
||||
if info.upper() == 'HELPME':
|
||||
return cmdhandler.get_help()
|
||||
self.userinfo.Command = info
|
||||
self.savesession()
|
||||
return "确认执行: " + info + " 命令?"
|
||||
rsp = tuling.getdata(info)
|
||||
return rsp
|
||||
|
||||
|
||||
class WxUserInfo():
|
||||
def __init__(self):
|
||||
self.isAdmin = False
|
||||
self.isPasswordSet = False
|
||||
self.Count = 0
|
||||
self.Command = ''
|
||||
|
||||
|
||||
"""
|
||||
@robot.handler
|
||||
def hello(message, session):
|
||||
blogapi = BlogApi()
|
||||
result = blogapi.search_articles(message.content)
|
||||
if result:
|
||||
articles = list(map(lambda x: x.object, result))
|
||||
reply = convert_to_articlereply(articles, message)
|
||||
return reply
|
||||
else:
|
||||
return '没有找到相关文章。'
|
||||
"""
|
||||
@@ -0,0 +1,82 @@
|
||||
from django.test import Client, RequestFactory, TestCase
|
||||
from django.contrib.sites.models import Site
|
||||
from .models import commands
|
||||
import datetime
|
||||
from accounts.models import BlogUser
|
||||
from blog.models import Category, Article
|
||||
from .robot import search, category, recents
|
||||
from werobot.messages.messages import TextMessage
|
||||
from .robot import MessageHandler, CommandHandler
|
||||
from servermanager.Api.commonapi import TuLing
|
||||
|
||||
|
||||
# Create your tests here.
|
||||
class ServerManagerTest(TestCase):
|
||||
def setUp(self):
|
||||
self.client = Client()
|
||||
self.factory = RequestFactory()
|
||||
|
||||
def test_tuling(self):
|
||||
t = TuLing()
|
||||
content = t.getdata('test')
|
||||
self.assertIsNotNone(content)
|
||||
|
||||
def test_validate_comment(self):
|
||||
site = Site.objects.get_current().domain
|
||||
user = BlogUser.objects.create_superuser(email="liangliangyy1@gmail.com",
|
||||
username="liangliangyy1", password="liangliangyy1")
|
||||
|
||||
self.client.login(username='liangliangyy1', password='liangliangyy1')
|
||||
|
||||
c = Category()
|
||||
c.name = "categoryccc"
|
||||
c.created_time = datetime.datetime.now()
|
||||
c.last_mod_time = datetime.datetime.now()
|
||||
c.save()
|
||||
|
||||
article = Article()
|
||||
article.title = "nicetitleccc"
|
||||
article.body = "nicecontentccc"
|
||||
article.author = user
|
||||
article.category = c
|
||||
article.type = 'a'
|
||||
article.status = 'p'
|
||||
article.save()
|
||||
s = TextMessage([])
|
||||
s.content = "nicetitleccc"
|
||||
rsp = search(s, None)
|
||||
self.assertTrue(rsp != '没有找到相关文章。')
|
||||
rsp = category(None, None)
|
||||
self.assertIsNotNone(rsp)
|
||||
rsp = recents(None, None)
|
||||
self.assertTrue(rsp != '暂时还没有文章')
|
||||
|
||||
cmd = commands()
|
||||
cmd.title = "test"
|
||||
cmd.command = "ls"
|
||||
cmd.describe = "test"
|
||||
cmd.save()
|
||||
|
||||
cmdhandler = CommandHandler()
|
||||
rsp = cmdhandler.run('test')
|
||||
self.assertIsNotNone(rsp)
|
||||
s.source = 'u'
|
||||
s.content = 'test'
|
||||
msghandler = MessageHandler(s, {})
|
||||
|
||||
#msghandler.userinfo.isPasswordSet = True
|
||||
#msghandler.userinfo.isAdmin = True
|
||||
msghandler.handler()
|
||||
s.content = 'y'
|
||||
msghandler.handler()
|
||||
s.content='idcard:12321233'
|
||||
msghandler.handler()
|
||||
s.content='weather:上海'
|
||||
msghandler.handler()
|
||||
s.content='admin'
|
||||
msghandler.handler()
|
||||
s.content='123'
|
||||
msghandler.handler()
|
||||
|
||||
s.content = 'exit'
|
||||
msghandler.handler()
|
||||
@@ -0,0 +1,23 @@
|
||||
#!/usr/bin/env python
|
||||
# encoding: utf-8
|
||||
|
||||
|
||||
"""
|
||||
@version: ??
|
||||
@author: liangliangyy
|
||||
@license: MIT Licence
|
||||
@contact: liangliangyy@gmail.com
|
||||
@site: https://www.lylinux.org/
|
||||
@software: PyCharm
|
||||
@file: urls.py
|
||||
@time: 2017/8/27 上午2:27
|
||||
"""
|
||||
|
||||
from django.conf.urls import url
|
||||
from werobot.contrib.django import make_view
|
||||
from .robot import robot
|
||||
|
||||
urlpatterns = [
|
||||
url(r'^robot/', make_view(robot)),
|
||||
|
||||
]
|
||||
@@ -0,0 +1,3 @@
|
||||
from django.shortcuts import render
|
||||
|
||||
# Create your views here.
|
||||
@@ -19,7 +19,7 @@
|
||||
{{ field.errors }}
|
||||
{% endfor %}
|
||||
|
||||
|
||||
<input type="hidden" name="next" value="{{ redirect_to }}">
|
||||
<button class="btn btn-lg btn-primary btn-block" type="submit">Sign in</button>
|
||||
|
||||
<div class="checkbox">
|
||||
@@ -28,7 +28,8 @@
|
||||
<input type="checkbox" value="remember-me"> Stay signed in
|
||||
</label>
|
||||
</div>
|
||||
|
||||
{% load oauth_tags %}
|
||||
{% load_oauth_applications request%}
|
||||
</form>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -4,6 +4,9 @@
|
||||
<title>{{ article.title }} | {{ SITE_DESCRIPTION }}</title>
|
||||
<meta property="og:type" content="article"/>
|
||||
<meta property="og:title" content="{{ article.title }}"/>
|
||||
|
||||
|
||||
<meta property="og:description" content="{{ article.body|custom_markdown|striptags|truncatewords:20 }}"/>
|
||||
<meta property="og:url"
|
||||
content="{{ article.get_full_url }}"/>
|
||||
<meta property="article:published_time" content="{% datetimeformat article.pub_time %}"/>
|
||||
@@ -42,7 +45,18 @@
|
||||
{% comment %}{% load comments_tags %}
|
||||
{% load_post_comment article from %}{% endcomment %}
|
||||
{% include 'comments/tags/comment_list.html' %}
|
||||
{% include 'comments/tags/post_comment.html' %}
|
||||
{% if user.is_authenticated %}
|
||||
{% include 'comments/tags/post_comment.html' %}
|
||||
{% else %}
|
||||
<div class="comments-area">
|
||||
<h3 class="comment-meta">您还没有登录,请您<a
|
||||
href="{% url "account:login" %}?next={{ request.get_full_path }}">登录</a>后发表评论。</h3>
|
||||
|
||||
{% load oauth_tags %}
|
||||
{% load_oauth_applications request%}
|
||||
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
</div><!-- #primary -->
|
||||
|
||||
|
||||
@@ -1,15 +1,14 @@
|
||||
{% extends 'share_layout/base.html' %}
|
||||
{% load blog_tags %}
|
||||
|
||||
{% load cache %}
|
||||
{% block header %}
|
||||
{% if tag_name %}
|
||||
<title>{{ page_type }}:{{ tag_name }} | {{ SITE_DESCRIPTION }}</title>
|
||||
<meta name="description" content="{{ page_type }}:{{ tag_name }}"/>
|
||||
{% comment %}<meta name="description" content="{{ page_type }}:{{ tag_name }}"/>{% endcomment %}
|
||||
{% else %}
|
||||
<title>{{ SITE_NAME }} | {{ SITE_DESCRIPTION }}</title>
|
||||
<meta name="description" content="{{ SITE_SEO_DESCRIPTION }}"/>
|
||||
{% endif %}
|
||||
|
||||
<meta name="description" content="{{ SITE_SEO_DESCRIPTION }}"/>
|
||||
<meta name="keywords" content="{{ SITE_KEYWORDS }}"/>
|
||||
<meta property="og:type" content="blog"/>
|
||||
<meta property="og:title" content="{{ SITE_NAME }}"/>
|
||||
@@ -55,7 +54,7 @@
|
||||
|
||||
|
||||
{% block sidebar %}
|
||||
{% load_sidebar request.user %}
|
||||
{% load_sidebar user %}
|
||||
{% endblock %}
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
{% extends 'share_layout/base.html' %}
|
||||
{% load blog_tags %}
|
||||
{% load cache %}
|
||||
{% block header %}
|
||||
{% if tag_name %}
|
||||
{% if statuscode == '404' %}
|
||||
<title>404 NotFound</title>
|
||||
{% elif statuscode == '500' %}
|
||||
<title>Permission Denied</title>
|
||||
{% elif statuscode == '403' %}
|
||||
<title>500 Error</title>
|
||||
{% else %}
|
||||
<title></title>
|
||||
{% endif %}
|
||||
{% comment %}<meta name="description" content="{{ page_type }}:{{ tag_name }}"/>{% endcomment %}
|
||||
{% else %}
|
||||
<title>{{ SITE_NAME }} | {{ SITE_DESCRIPTION }}</title>
|
||||
{% endif %}
|
||||
<meta name="description" content="{{ SITE_SEO_DESCRIPTION }}"/>
|
||||
<meta name="keywords" content="{{ SITE_KEYWORDS }}"/>
|
||||
<meta property="og:type" content="blog"/>
|
||||
<meta property="og:title" content="{{ SITE_NAME }}"/>
|
||||
<meta property="og:description" content="{{ SITE_DESCRIPTION }}"/>
|
||||
<meta property="og:url" content="{{ SITE_BASE_URL }}"/>
|
||||
<meta property="og:site_name" content="{{ SITE_NAME }}"/>
|
||||
{% endblock %}
|
||||
{% block content %}
|
||||
<div id="primary" class="site-content">
|
||||
<div id="content" role="main">
|
||||
|
||||
<header class="archive-header">
|
||||
<h1 class="archive-title">{{ message }}</h1>
|
||||
</header><!-- .archive-header -->
|
||||
|
||||
</div><!-- #content -->
|
||||
</div><!-- #primary -->
|
||||
|
||||
{% endblock %}
|
||||
|
||||
|
||||
{% block sidebar %}
|
||||
{% load_sidebar user %}
|
||||
{% endblock %}
|
||||
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
{% load blog_tags %}
|
||||
<article id="post-{{ article.pk }} "
|
||||
class="post-3815 post type-post status-publish format-standard hentry"
|
||||
itemscope itemtype="http://schema.org/Article">
|
||||
class="post-{{ article.pk }} post type-post status-publish format-standard hentry">
|
||||
<header class="entry-header">
|
||||
|
||||
<h1 class="entry-title" itemprop="name">
|
||||
<h1 class="entry-title">
|
||||
{% if isindex %}
|
||||
<a href="{{ article.get_absolute_url }}"
|
||||
rel="bookmark">{{ article.title }}</a>
|
||||
@@ -39,7 +38,7 @@
|
||||
|
||||
<div class="entry-content" itemprop="articleBody">
|
||||
{% if isindex %}
|
||||
{{ article.body|truncatechars_content|custom_markdown }}
|
||||
{{ article.body|custom_markdown|truncatechars_content }}
|
||||
<p class='read-more'><a
|
||||
href=' {{ article.get_absolute_url }}'>Read more</a></p>
|
||||
{% else %}
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
itemprop="datePublished" content="{% datetimeformat article.created_time %}"
|
||||
rel="bookmark">
|
||||
|
||||
<time class="entry-date"
|
||||
<time class="entry-date updated"
|
||||
datetime="{{ article.created_time }}">
|
||||
{% datetimeformat article.created_time %}</time>
|
||||
</a>
|
||||
|
||||
@@ -8,6 +8,17 @@
|
||||
</div>
|
||||
</form>
|
||||
</aside>
|
||||
{% if extra_sidebars %}
|
||||
{% for sidebar in extra_sidebars %}
|
||||
|
||||
<aside class="widget_text widget widget_custom_html"><h3 class="widget-title">
|
||||
{{ sidebar.name }}</h3>
|
||||
<div class="textwidget custom-html-widget">
|
||||
{{ sidebar.content }}
|
||||
</div>
|
||||
</aside>
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
{% if most_read_articles %}
|
||||
|
||||
<aside id="views-4" class="widget widget_views"><h3 class="widget-title">Views</h3>
|
||||
@@ -41,8 +52,7 @@
|
||||
{% for c in sidebar_comments %}
|
||||
<li class="recentcomments">
|
||||
<span class="comment-author-link">
|
||||
<a href="{{ c.author.get_absolute_url }}" rel="external"
|
||||
class="url">{{ c.author.username }}</a></span>
|
||||
{{ c.author.username }}</span>
|
||||
发表在《
|
||||
<a href="{{ c.article.get_absolute_url }}">{{ c.article.title }}</a>》
|
||||
</li>
|
||||
@@ -77,6 +87,28 @@
|
||||
{% if show_adsense %}
|
||||
{% include 'share_layout/adsense.html' %}
|
||||
{% endif %}
|
||||
{% if sidebar_tags %}
|
||||
<aside id="tag_cloud-2" class="widget widget_tag_cloud"><h3 class="widget-title">标签云</h3>
|
||||
<div class="tagcloud">
|
||||
{% for tag,count,size in sidebar_tags %}
|
||||
<a href="{{ tag.get_absolute_url }}"
|
||||
class="tag-link-{{ tag.id }} tag-link-position-{{ tag.id }}"
|
||||
style="font-size: {{ size }}pt;" title="{{ count }}个话题"> {{ tag.name }}
|
||||
</a>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</aside>
|
||||
{% endif %}
|
||||
<aside id="text-2" class="widget widget_text"><h3 class="widget-title">欢迎您star或者fork本站源代码</h3>
|
||||
<div class="textwidget">
|
||||
|
||||
<p><a href="https://github.com/liangliangyy/DjangoBlog" rel="nofollow"><img
|
||||
src="https://img.shields.io/github/stars/liangliangyy/djangoblog.svg?style=social&label=Star"
|
||||
alt="GitHub stars"></a> <a href="https://github.com/liangliangyy/DjangoBlog" rel="nofollow"><img
|
||||
src="https://img.shields.io/github/forks/liangliangyy/djangoblog.svg?style=social&label=Fork"
|
||||
alt="GitHub forks"></a></p>
|
||||
</div>
|
||||
</aside>
|
||||
<aside id="meta-2" class="widget widget_meta"><h3 class="widget-title">功能</h3>
|
||||
<ul>
|
||||
<li><a href="{{ SITE_BASE_URL }}/admin/">管理站点</a></li>
|
||||
@@ -87,8 +119,8 @@
|
||||
{% else %}
|
||||
<li><a href="{% url "account:login" %}">登录</a>
|
||||
{% endif %}
|
||||
|
||||
<li><a href="http://gitbook.lylinux.net" target="_blank" rel="nofollow">GitBook</a></li>
|
||||
</ul>
|
||||
</aside>
|
||||
|
||||
<div id="rocket" class="show" title="点我返回顶部"></div>
|
||||
</div><!-- #secondary -->
|
||||
@@ -7,7 +7,13 @@
|
||||
srcset="{{ comment_item.author.email|gravatar_url:150 }}"
|
||||
class="avatar avatar-96 photo" height="96" width="96">
|
||||
<cite class="fn">
|
||||
<a rel="nofollow" href="{{ comment_item.author.get_absolute_url }}" rel="external nofollow"
|
||||
<a rel="nofollow"
|
||||
{% if comment_item.author.is_superuser %}
|
||||
href="{{ comment_item.author.get_absolute_url }}"
|
||||
{% else %}
|
||||
href="#"
|
||||
{% endif %}
|
||||
rel="external nofollow"
|
||||
class="url">{{ comment_item.author.username }}
|
||||
</a>
|
||||
</cite>
|
||||
@@ -18,7 +24,7 @@
|
||||
{{ comment_item.created_time }}
|
||||
</div>
|
||||
|
||||
<p>{{ comment_item.body }}</p>
|
||||
<p>{{ comment_item.body |custom_markdown }}</p>
|
||||
|
||||
<div class="reply"><a rel="nofollow" class="comment-reply-link"
|
||||
href="javascript:void(0)"
|
||||
|
||||
@@ -47,13 +47,13 @@
|
||||
|
||||
</p>{% endcomment %}
|
||||
{{ form.parent_comment_id }}
|
||||
|
||||
<p class="form-submit">
|
||||
<div class="form-submit">
|
||||
<span class="comment-markdown"> 支持markdown</span>
|
||||
<input name="submit" type="submit" id="submit" class="submit" value="发表评论"/>
|
||||
<small class="cancel-comment" id="cancel_comment" style="display: none">
|
||||
<a href="javascript:void(0)" id="cancel-comment-reply-link" onclick="cancel_reply()">取消回复</a>
|
||||
</small>
|
||||
</p>
|
||||
</div>
|
||||
</form>
|
||||
</div><!-- #respond -->
|
||||
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
{% extends 'share_layout/base.html' %}
|
||||
{% block header %}
|
||||
<title> {{ title }}</title>
|
||||
{% endblock %}
|
||||
{% block content %}
|
||||
<div id="primary" class="site-content">
|
||||
<div id="content" role="main">
|
||||
|
||||
<header class="archive-header">
|
||||
|
||||
<h2 class="archive-title"> {{ content }}</h2>
|
||||
</header><!-- .archive-header -->
|
||||
<br/>
|
||||
<header class="archive-header" style="text-align: center">
|
||||
|
||||
<a href="{% url "account:login" %}">Sign In</a>
|
||||
|
|
||||
<a href="/">Home Page</a>
|
||||
</header><!-- .archive-header -->
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
@@ -0,0 +1,10 @@
|
||||
<div class="widget-login">
|
||||
<small>
|
||||
快捷登录:
|
||||
</small>
|
||||
{% for icon,url in apps %}
|
||||
<a href="{{ url }}" rel="nofollow">
|
||||
<span class="icon-sn-{{ icon }}"></span>
|
||||
</a>
|
||||
{% endfor %}
|
||||
</div>
|
||||
@@ -0,0 +1,46 @@
|
||||
{% extends 'share_layout/base_account.html' %}
|
||||
|
||||
{% load static %}
|
||||
{% block content %}
|
||||
<div class="container">
|
||||
|
||||
<h2 class="form-signin-heading text-center">Binding E-mail account</h2>
|
||||
|
||||
<div class="card card-signin">
|
||||
{% if picture %}
|
||||
<img class="img-circle profile-img" src="{{ picture }}" alt="">
|
||||
{% else %}
|
||||
<img class="img-circle profile-img" src="{% static 'blog/img/avatar.png' %}" alt="">
|
||||
{% endif %}
|
||||
<form class="form-signin" action="" method="post">
|
||||
{% csrf_token %}
|
||||
{% comment %}<label for="inputEmail" class="sr-only">Email address</label>
|
||||
<input type="email" id="inputEmail" class="form-control" placeholder="Email" required autofocus>
|
||||
<label for="inputPassword" class="sr-only">Password</label>
|
||||
<input type="password" id="inputPassword" class="form-control" placeholder="Password" required>{% endcomment %}
|
||||
{{ form.non_field_errors }}
|
||||
{% for field in form %}
|
||||
{{ field }}
|
||||
{{ field.errors }}
|
||||
{% endfor %}
|
||||
|
||||
|
||||
<button class="btn btn-lg btn-primary btn-block" type="submit">Submit</button>
|
||||
|
||||
{% comment %}
|
||||
<div class="checkbox">
|
||||
<a class="pull-right">Need help?</a>
|
||||
<label>
|
||||
<input type="checkbox" value="remember-me"> Stay signed in
|
||||
</label>
|
||||
</div>
|
||||
{% endcomment %}
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<p class="text-center">
|
||||
<a href="{% url "account:login" %}">Sign In</a>
|
||||
</p>
|
||||
|
||||
</div> <!-- /container -->
|
||||
{% endblock %}
|
||||
@@ -1,12 +1,9 @@
|
||||
<script type="text/javascript">
|
||||
var cnzz_protocol = (("https:" == document.location.protocol) ? " https://" : " http://");
|
||||
document.write(unescape("%3Cspan id='cnzz_stat_icon_1261145394'%3E%3C/span%3E%3Cscript src='" + cnzz_protocol + "s4.cnzz.com/stat.php%3Fid%3D1261145394%26show%3Dpic' type='text/javascript'%3E%3C/script%3E"));
|
||||
|
||||
(function (i, s, o, g, r, a, m) {
|
||||
i['GoogleAnalyticsObject'] = r;
|
||||
i[r] = i[r] || function () {
|
||||
(i[r].q = i[r].q || []).push(arguments)
|
||||
}, i[r].l = 1 * new Date();
|
||||
(i[r].q = i[r].q || []).push(arguments)
|
||||
}, i[r].l = 1 * new Date();
|
||||
a = s.createElement(o),
|
||||
m = s.getElementsByTagName(o)[0];
|
||||
a.async = 1;
|
||||
@@ -16,5 +13,12 @@
|
||||
|
||||
ga('create', 'UA-27354903-4', 'auto');
|
||||
ga('send', 'pageview');
|
||||
|
||||
</script>
|
||||
var _hmt = _hmt || [];
|
||||
(function () {
|
||||
var hm = document.createElement("script");
|
||||
hm.src = "https://hm.baidu.com/hm.js?30dd00aec5f76607ba37c6770ae48e45";
|
||||
var s = document.getElementsByTagName("script")[0];
|
||||
s.parentNode.insertBefore(hm, s);
|
||||
})();
|
||||
</script>
|
||||
<a href="https://tongji.baidu.com/sc-web/3478620/home/ico?siteId=11261596" target="_blank" rel="nofollow">百度统计</a>
|
||||
@@ -17,7 +17,7 @@
|
||||
{% block header %}
|
||||
{% endblock %}
|
||||
<link rel="profile" href="http://gmpg.org/xfn/11"/>
|
||||
{% comment %}<link rel="pingback" href="https://www.lylinux.org/xmlrpc.php"/>{% endcomment %}
|
||||
|
||||
<!--[if lt IE 9]>
|
||||
<script src="{% static 'blog/js/html5.js' %}" type="text/javascript"></script>
|
||||
<![endif]-->
|
||||
@@ -32,19 +32,16 @@
|
||||
{% compress css %}
|
||||
<link rel='stylesheet' id='twentytwelve-style-css' href='{% static 'blog/css/style.css' %}' type='text/css'
|
||||
media='all'/>
|
||||
<link href="{% static 'blog/css/oauth_style.css' %}" rel="stylesheet">
|
||||
{% comment %}<script src="https://cdn.rawgit.com/google/code-prettify/master/loader/run_prettify.js"></script>{% endcomment %}
|
||||
<!--[if lt IE 9]>
|
||||
<link rel='stylesheet' id='twentytwelve-ie-css' href='{% static 'blog/css/ie.css' %}' type='text/css' media='all' />
|
||||
<![endif]-->
|
||||
<![endif]-->
|
||||
<link rel="stylesheet" href="{% static 'pygments/default.css' %}"/>
|
||||
<link rel="stylesheet" href="{% static 'blog/css/nprogress.css' %}">
|
||||
{% block compress_css %}
|
||||
{% endblock %}
|
||||
{% endcompress %}
|
||||
{% comment %}<script type='text/javascript' src='{% static 'blog/js/jquery.js' %}' defer='defer'></script>
|
||||
<script type='text/javascript' src='{% static 'blog/js/jquery-migrate.min.js' %}' defer='defer'></script>{% endcomment %}
|
||||
{% compress js %}
|
||||
<script type="text/javascript" src="{% static 'blog/js/jquery-3.1.1.js' %}"></script>
|
||||
{% endcompress %}
|
||||
</head>
|
||||
|
||||
<body class="home blog custom-font-enabled">
|
||||
@@ -74,24 +71,12 @@
|
||||
</div><!-- #main .wrapper -->
|
||||
{% include 'share_layout/footer.html' %}
|
||||
</div><!-- #page -->
|
||||
{% comment %}
|
||||
<div id="wp-auto-top">
|
||||
<div id="wp-auto-top-top"></div>
|
||||
<div id="wp-auto-top-bottom"></div>
|
||||
</div>
|
||||
<script> var commentPositionId = '.ds-comments:last';
|
||||
var wpAutoTopSpeed = 1;</script>
|
||||
|
||||
<div id="su-footer-links" style="text-align: center;"></div>
|
||||
{% endcomment %}
|
||||
{% comment %}<script type="application/javascript" src="{% static 'blog/js/navigation.js' %}" defer="defer"></script>{% endcomment %}
|
||||
|
||||
</body>
|
||||
<footer>
|
||||
{% comment %}<link href="{% static 'highlight/styles/default.css' %}" rel="stylesheet">
|
||||
<script type="application/javascript" src="{% static 'highlight/highlight.pack.js' %}"></script>
|
||||
<script>hljs.initHighlightingOnLoad();</script>{% endcomment %}
|
||||
{% compress js %}
|
||||
<script type="text/javascript" src="{% static 'blog/js/jquery-3.1.1.js' %}"></script>
|
||||
<script type="text/javascript" src="{% static 'blog/js/nprogress.js' %}"></script>
|
||||
<script src="{% static 'blog/js/blog.js' %}" type="application/javascript"></script>
|
||||
<script src="{% static 'blog/js/navigation.js' %}" type="application/javascript"></script>
|
||||
{% block compress_js %}
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
{% compress css %}
|
||||
<!-- Bootstrap core CSS -->
|
||||
<link href="{% static 'assets/css/bootstrap.min.css' %}" rel="stylesheet">
|
||||
|
||||
<link href="{% static 'blog/css/oauth_style.css' %}" rel="stylesheet">
|
||||
<!-- IE10 viewport hack for Surface/desktop Windows 8 bug -->
|
||||
<link href="{% static 'assets/css/ie10-viewport-bug-workaround.css' %}" rel="stylesheet">
|
||||
<!-- TODC Bootstrap core CSS -->
|
||||
|
||||
@@ -21,7 +21,9 @@
|
||||
Powered by
|
||||
<a href="https://www.djangoproject.com/" rel="nofollow" target="blank">Django</a>
|
||||
|
|
||||
<a href="https://github.com/liangliangyy/DjangoBlog" rel="nofollow" target="blank"> LiangLiangyy</a>
|
||||
<a href="https://github.com/liangliangyy/DjangoBlog" rel="nofollow" target="blank">LiangLiangyy</a>
|
||||
|
|
||||
<a href="https://www.lylinux.net" target="blank">Lylinux</a>
|
||||
|
|
||||
本页面加载耗时:<!!LOAD_TIMES!!>s
|
||||
</div>
|
||||
|
||||
@@ -1,25 +0,0 @@
|
||||
Django==1.10.5
|
||||
django-appconf==1.0.2
|
||||
django-autoslug==1.9.3
|
||||
django-compressor==2.1.1
|
||||
django-debug-toolbar==1.6
|
||||
django-haystack==2.6.0
|
||||
django-ipware==1.1.6
|
||||
django-pagedown==0.1.1
|
||||
django-uuslug==1.1.8
|
||||
jieba==0.38
|
||||
markdown2==2.3.2
|
||||
mistune==0.7.3
|
||||
Pillow==4.0.0
|
||||
Pygments==2.1.3
|
||||
PyMySQL==0.7.9
|
||||
python-slugify==1.2.1
|
||||
pytz==2016.10
|
||||
rcssmin==1.0.6
|
||||
requests==2.12.4
|
||||
rjsmin==1.0.12
|
||||
six==1.10.0
|
||||
sqlparse==0.2.2
|
||||
Unidecode==0.4.20
|
||||
webencodings==0.5
|
||||
Whoosh==2.7.4
|
||||
@@ -0,0 +1,33 @@
|
||||
appdirs==1.4.3
|
||||
coverage==4.4.1
|
||||
Django==1.11.7
|
||||
django-appconf==1.0.2
|
||||
django-autoslug==1.9.3
|
||||
django-compressor==2.2
|
||||
django-debug-toolbar==1.9.1
|
||||
django-haystack==2.6.1
|
||||
django-ipware==1.1.6
|
||||
django-pagedown==0.1.3
|
||||
django-uuslug==1.1.8
|
||||
jieba==0.39
|
||||
jsonpickle==0.9.5
|
||||
markdown2==2.3.5
|
||||
mistune==0.8.1
|
||||
olefile==0.44
|
||||
packaging==16.8
|
||||
Pillow==4.3.0
|
||||
Pygments==2.2.0
|
||||
PyMySQL==0.7.11
|
||||
pyparsing==2.2.0
|
||||
python-slugify==1.2.4
|
||||
pytz==2017.3
|
||||
rcssmin==1.0.6
|
||||
requests==2.18.4
|
||||
rjsmin==1.0.12
|
||||
six==1.11.0
|
||||
sqlparse==0.2.4
|
||||
Unidecode==0.4.21
|
||||
urllib3==1.22
|
||||
webencodings==0.5.1
|
||||
WeRoBot==1.1.1
|
||||
Whoosh==2.7.4
|
||||
@@ -9,7 +9,7 @@ https://docs.djangoproject.com/en/1.10/topics/settings/
|
||||
For the full list of settings and their values, see
|
||||
https://docs.djangoproject.com/en/1.10/ref/settings/
|
||||
"""
|
||||
|
||||
import sys
|
||||
import os
|
||||
|
||||
# Build paths inside the project like this: os.path.join(BASE_DIR, ...)
|
||||
@@ -25,8 +25,11 @@ SECRET_KEY = '&3g0bdza#c%dm1lf%5gi&0-*53p3t0m*hmcvo29cn^$ji7je(c'
|
||||
DEBUG = True
|
||||
# DEBUG = False
|
||||
|
||||
|
||||
TESTING = len(sys.argv) > 1 and sys.argv[1] == 'test'
|
||||
|
||||
# ALLOWED_HOSTS = []
|
||||
ALLOWED_HOSTS = ['www.lylinux.net', '127.0.0.1']
|
||||
ALLOWED_HOSTS = ['www.lylinux.net', '127.0.0.1', 'example.com']
|
||||
# Application definition
|
||||
|
||||
INSTALLED_APPS = [
|
||||
@@ -44,6 +47,7 @@ INSTALLED_APPS = [
|
||||
'accounts',
|
||||
'comments',
|
||||
'oauth',
|
||||
'servermanager',
|
||||
'compressor'
|
||||
]
|
||||
|
||||
@@ -51,7 +55,9 @@ MIDDLEWARE_CLASSES = [
|
||||
'django.middleware.security.SecurityMiddleware',
|
||||
'django.contrib.sessions.middleware.SessionMiddleware',
|
||||
'django.middleware.gzip.GZipMiddleware',
|
||||
# 'django.middleware.cache.UpdateCacheMiddleware',
|
||||
'django.middleware.common.CommonMiddleware',
|
||||
# 'django.middleware.cache.FetchFromCacheMiddleware',
|
||||
'django.middleware.csrf.CsrfViewMiddleware',
|
||||
'django.contrib.auth.middleware.AuthenticationMiddleware',
|
||||
'django.contrib.messages.middleware.MessageMiddleware',
|
||||
@@ -192,19 +198,29 @@ CACHE_MIDDLEWARE_SECONDS = 60 * 60 * 10
|
||||
CACHE_MIDDLEWARE_KEY_PREFIX = "djangoblog"
|
||||
CACHE_MIDDLEWARE_ALIAS = 'default'
|
||||
|
||||
SESSION_ENGINE = "django.contrib.sessions.backends.cache"
|
||||
SESSION_CACHE_ALIAS = 'default'
|
||||
# SESSION_ENGINE = "django.contrib.sessions.backends.cache"
|
||||
# SESSION_CACHE_ALIAS = 'default'
|
||||
|
||||
OAHUTH = {
|
||||
'sina': {
|
||||
'appkey': '3161614143',
|
||||
'appsecret': 'ee17c099317f872eeddb25204ea46721',
|
||||
'callbackurl': 'http://www.lylinux.net/oauth/weibo'
|
||||
'callbackurl': 'http://www.lylinux.net/oauth/authorize?type=weibo'
|
||||
},
|
||||
'google': {
|
||||
'appkey': os.environ.get('GOOGLE_APP_KEY'),
|
||||
'appsecret': os.environ.get('GOOGLE_APP_SECRET'),
|
||||
'callbackurl': 'http://www.lylinux.net/oauth/googleauthorize'
|
||||
'appkey': 'googleappkey',
|
||||
'appsecret': 'googleappsecret',
|
||||
'callbackurl': 'http://www.lylinux.net/oauth/authorize?type=google'
|
||||
},
|
||||
'github': {
|
||||
'appkey': 'githubappkey',
|
||||
'appsecret': 'githubappsecret',
|
||||
'callbackurl': 'http://www.lylinux.net/oauth/authorize?type=github'
|
||||
},
|
||||
'facebook': {
|
||||
'appkey': 'facebookappkey',
|
||||
'appsecret': 'facebookappsecret',
|
||||
'callbackurl': 'http://www.lylinux.net/oauth/authorize?type=facebook'
|
||||
}
|
||||
}
|
||||
|
||||
@@ -225,7 +241,8 @@ DEFAULT_FROM_EMAIL = EMAIL_HOST_USER
|
||||
SERVER_EMAIL = os.environ.get('DJANGO_EMAIL_USER')
|
||||
# 设置debug=false 未处理异常邮件通知
|
||||
ADMINS = [('liangliang', 'liangliangyy@gmail.com')]
|
||||
|
||||
# 微信管理员密码(两次md5获得)
|
||||
WXADMIN = '995F03AC401D6CABABAEF756FC4D43C7'
|
||||
LOGGING = {
|
||||
'version': 1,
|
||||
'disable_existing_loggers': False,
|
||||
Reference in New Issue
Block a user