diff --git a/DjangoBlog/settings.py b/DjangoBlog/settings.py
index 569679d..6300ac7 100644
--- a/DjangoBlog/settings.py
+++ b/DjangoBlog/settings.py
@@ -19,14 +19,14 @@ 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
# 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 = [
@@ -51,9 +51,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',
@@ -195,19 +195,24 @@ 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'
+ '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'
}
}
diff --git a/DjangoBlog/utils.py b/DjangoBlog/utils.py
index cdfcfda..4326680 100644
--- a/DjangoBlog/utils.py
+++ b/DjangoBlog/utils.py
@@ -19,12 +19,10 @@ from pygments import highlight
from pygments.lexers import get_lexer_by_name
from pygments.formatters import html
import logging
+import _thread
+from django.core.mail import EmailMultiAlternatives
logger = logging.getLogger('djangoblog')
-from importlib import import_module
-from django.conf import settings
-
-SessionStore = import_module(settings.SESSION_ENGINE).SessionStore
def get_max_articleid_commentid():
@@ -120,3 +118,24 @@ class common_markdown():
mdp = mistune.Markdown(escape=True, renderer=renderer)
return mdp(value)
+
+
+def send_email(subject, html_content, tomail):
+ msg = EmailMultiAlternatives(subject, html_content, from_email='no-reply@lylinux.net', to=tomail)
+ msg.content_subtype = "html"
+
+ def send_comment_email(msg):
+ try:
+ msg.send()
+ except:
+ print('send email error')
+ pass
+
+ _thread.start_new_thread(send_comment_email, (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
diff --git a/blog/admin.py b/blog/admin.py
index 5c646ed..b4173d7 100644
--- a/blog/admin.py
+++ b/blog/admin.py
@@ -16,6 +16,11 @@ 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 = ('author', 'status', 'type', 'category', 'tags')
+ filter_horizontal = ('tags',)
+ exclude = ('slug', 'created_time')
def save_model(self, request, obj, form, change):
super(ArticlelAdmin, self).save_model(request, obj, form, change)
@@ -23,8 +28,16 @@ class ArticlelAdmin(admin.ModelAdmin):
cache.clear()
+class TagAdmin(admin.ModelAdmin):
+ exclude = ('slug',)
+
+
+class CategoryAdmin(admin.ModelAdmin):
+ exclude = ('slug',)
+
+
admin.site.register(Article, ArticlelAdmin)
# admin.site.register(BlogPage, ArticlelAdmin)
-admin.site.register(Category)
-admin.site.register(Tag)
+admin.site.register(Category, CategoryAdmin)
+admin.site.register(Tag, TagAdmin)
admin.site.register(Links)
diff --git a/blog/management/commands/create_testdata.py b/blog/management/commands/create_testdata.py
new file mode 100644
index 0000000..aa41421
--- /dev/null
+++ b/blog/management/commands/create_testdata.py
@@ -0,0 +1,44 @@
+#!/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='testuser',
+ password='test!q@w#eTYU')[0]
+
+ pcategory = Category.objects.get_or_create(name='pcategory', parent_category=None)[0]
+
+ category = Category.objects.get_or_create(name='category', parent_category=pcategory)[0]
+
+ category.save()
+ for i in range(1, 10):
+ article = Article.objects.get_or_create(category=category,
+ title='nice title ' + str(i),
+ body='nice content ' + str(i),
+ author=user
+ )
+ from DjangoBlog.utils import cache
+ cache.clear()
+ self.stdout.write(self.style.SUCCESS('created test datas \n'))
diff --git a/blog/models.py b/blog/models.py
index 8331a9a..169df92 100644
--- a/blog/models.py
+++ b/blog/models.py
@@ -141,70 +141,6 @@ 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)
diff --git a/blog/static/blog/css/nprogress.css b/blog/static/blog/css/nprogress.css
new file mode 100644
index 0000000..90c7b6c
--- /dev/null
+++ b/blog/static/blog/css/nprogress.css
@@ -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); }
+}
+
diff --git a/blog/static/blog/css/oauth_style.css b/blog/static/blog/css/oauth_style.css
new file mode 100644
index 0000000..8af78af
--- /dev/null
+++ b/blog/static/blog/css/oauth_style.css
@@ -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;
+}
\ No newline at end of file
diff --git a/blog/static/blog/css/style.css b/blog/static/blog/css/style.css
index fe85ae8..45b560e 100755
--- a/blog/static/blog/css/style.css
+++ b/blog/static/blog/css/style.css
@@ -1998,6 +1998,9 @@ div {
#wp-auto-top-bottom:hover {
background-position: right -68px;
}
+.widget-login{
+ margin-top: 15px!important;
+}
/* ------------------------------------------------------------------------- *
* Comments
@@ -2358,308 +2361,3 @@ li #reply-title {
font: inherit;
vertical-align: baseline;
}
-
-.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;
-}
\ No newline at end of file
diff --git a/blog/static/blog/js/blog.js b/blog/static/blog/js/blog.js
index 4551434..a6fa29c 100644
--- a/blog/static/blog/js/blog.js
+++ b/blog/static/blog/js/blog.js
@@ -17,3 +17,14 @@ 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);
+});
\ No newline at end of file
diff --git a/blog/static/blog/js/nprogress.js b/blog/static/blog/js/nprogress.js
new file mode 100644
index 0000000..beb9d2c
--- /dev/null
+++ b/blog/static/blog/js/nprogress.js
@@ -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: '
'
+ };
+
+ /**
+ * 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;
+});
diff --git a/blog/views.py b/blog/views.py
index b53a9c2..067b0cb 100644
--- a/blog/views.py
+++ b/blog/views.py
@@ -29,33 +29,6 @@ from django.utils.decorators import classonlymethod
from django.utils.decorators import method_decorator
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)
-"""
-
class ArticleListView(ListView):
# template_name属性用于指定使用哪个模板进行渲染
@@ -117,24 +90,6 @@ class IndexView(ArticleListView):
cache_key = 'index_{page}'.format(page=self.page_number)
return cache_key
- '''
- def get_queryset(self):
- # return self.get_queryset_data()
- cache_key = 'index_{page}'.format(page=self.page_number)
- return self.get_queryset_from_cache(cache_key=cache_key)
- """
- value = cache.get(cache_key)
- if value:
- logger.info('get view cache.key:{key}'.format(key=cache_key))
- return value
- else:
- article_list = Article.objects.filter(type='a', status='p')
- cache.set(cache_key, article_list)
- logger.info('set view cache.key:{key}'.format(key=cache_key))
- return article_list
- """
- '''
-
class ArticleDetailView(DetailView):
template_name = 'blog/article_detail.html'
@@ -175,37 +130,6 @@ class ArticleDetailView(DetailView):
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 = "分类目录归档"
@@ -226,35 +150,8 @@ class CategoryDetailView(ArticleListView):
cache_key = 'category_list_{categoryname}_{page}'.format(categoryname=categoryname, page=self.page_number)
return cache_key
- '''
- def get_queryset(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
-
- cache_key = 'category_list_{categoryname}_{page}'.format(categoryname=categoryname, page=self.page_number)
-
- value = cache.get(cache_key)
- if value:
- logger.info('get view cache.key:{key}'.format(key=cache_key))
- return value
- else:
- article_list = Article.objects.filter(category__name=categoryname, status='p')
- cache.set(cache_key, article_list)
- logger.info('set view cache.key:{key}'.format(key=cache_key))
- return article_list
- '''
-
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]
@@ -360,25 +257,18 @@ def refresh_memcache(request):
"""
-class BlogSearchView(SearchView):
- form_class = BlogSearchForm
- template_name = 'blog/article_detail.html'
- model = Article
- # template_name属性用于指定使用哪个模板进行渲染
- template_name = 'blog/article_index.html'
+class SeoProcessor():
+ __metaclass__ = ABCMeta
- # context_object_name属性用于给上下文变量取名(在模板中使用该名字)
- context_object_name = 'article_list'
+ @abstractmethod
+ def get_title(self):
+ pass
- 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
+ @abstractmethod
+ def get_keywords(self):
+ pass
- 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)
+ @abstractmethod
+ def get_description(self):
+ pass
"""
diff --git a/comments/models.py b/comments/models.py
index 61650f3..106e5cf 100644
--- a/comments/models.py
+++ b/comments/models.py
@@ -11,9 +11,6 @@ from DjangoBlog.utils import cache
# 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)
diff --git a/comments/views.py b/comments/views.py
index c5bcf10..787f397 100644
--- a/comments/views.py
+++ b/comments/views.py
@@ -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'
diff --git a/oauth/forms.py b/oauth/forms.py
new file mode 100644
index 0000000..86851f8
--- /dev/null
+++ b/oauth/forms.py
@@ -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"})
diff --git a/oauth/models.py b/oauth/models.py
index 76a10b0..33ec0da 100644
--- a/oauth/models.py
+++ b/oauth/models.py
@@ -4,33 +4,14 @@ from django.db import models
from django.conf import settings
-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)
+ 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)
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_plural = verbose_name
diff --git a/oauth/oauthmanager.py b/oauth/oauthmanager.py
index fce22d4..c22a1ac 100644
--- a/oauth/oauthmanager.py
+++ b/oauth/oauthmanager.py
@@ -14,12 +14,15 @@
"""
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"""
@@ -29,10 +32,7 @@ class BaseManager(metaclass=ABCMeta):
'''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
@@ -65,14 +65,17 @@ 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):
params = {
@@ -84,7 +87,7 @@ class WBOauthManager(BaseManager):
return url
def get_access_token_by_code(self, code):
- print(code)
+
params = {
'client_id': self.client_id,
'client_secret': self.client_secret,
@@ -93,7 +96,7 @@ class WBOauthManager(BaseManager):
'redirect_uri': self.callback_url
}
rsp = self.do_post(self.TOKEN_URL, params)
- print(rsp)
+
# return rsp
obj = json.loads(rsp)
@@ -116,18 +119,20 @@ class WBOauthManager(BaseManager):
'access_token': self.access_token
}
rsp = self.do_get(self.API_URL, params)
- print(rsp)
-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):
params = {
@@ -136,7 +141,8 @@ class GoogleOauthManager(BaseManager):
'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):
@@ -149,10 +155,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:
@@ -161,5 +173,95 @@ 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.picture = datas['picture']
+ user.nikename = datas['name']
+ user.openid = datas['sub']
+ 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):
+ params = {
+ 'client_id': self.client_id,
+ 'response_type': 'code',
+ 'redirect_uri': self.callback_url,
+ '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'
+ if datas['email']:
+ user.email = datas['email']
+
+ return user
+ except:
+ logger.info('github oauth error.rsp:' + rsp)
+ 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
diff --git a/oauth/templatetags/__init__.py b/oauth/templatetags/__init__.py
new file mode 100644
index 0000000..a8d9fd4
--- /dev/null
+++ b/oauth/templatetags/__init__.py
@@ -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
+"""
\ No newline at end of file
diff --git a/oauth/templatetags/oauth_tags.py b/oauth/templatetags/oauth_tags.py
new file mode 100644
index 0000000..eb37aab
--- /dev/null
+++ b/oauth/templatetags/oauth_tags.py
@@ -0,0 +1,29 @@
+#!/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 import template
+from django.conf import settings
+
+register = template.Library()
+
+
+@register.inclusion_tag('oauth/oauth_applications.html')
+def load_oauth_applications():
+ applications = get_oauth_apps()
+ apps = list(map(lambda x: (x.ICON_NAME, x.get_authorization_url()), applications))
+ return {
+ 'apps': apps
+ }
diff --git a/oauth/tests.py b/oauth/tests.py
index d65f3fd..7ce503c 100644
--- a/oauth/tests.py
+++ b/oauth/tests.py
@@ -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'])
\ No newline at end of file
diff --git a/oauth/urls.py b/oauth/urls.py
index b980ec2..37d3542 100644
--- a/oauth/urls.py
+++ b/oauth/urls.py
@@ -17,6 +17,13 @@ from django.conf.urls import url
from django.views.decorators.cache import cache_page
from . import views
+urlpatterns = [
+ url(r'^oauth/authorize$', views.authorize),
+ url(r'^oauth/requireemail/(?P\d+)', views.RequireEmailView.as_view(), name='require_email'),
+ url(r'^oauth/emailconfirm/(?P\d+)/(?P\S+)', views.emailconfirm, name='email_confirm'),
+]
+
+"""
urlpatterns = [
url(r'^oauth/wbauthorize/(?P\w+)$', views.wbauthorize),
url(r'^oauth/wboauthurl$', views.wboauthurl),
@@ -24,3 +31,4 @@ urlpatterns = [
url(r'^oauth/googleoauthurl', views.googleoauthurl),
url(r'^oauth/googleauthorize', views.googleauthorize),
]
+"""
diff --git a/oauth/views.py b/oauth/views.py
index a88051f..cc82d97 100644
--- a/oauth/views.py
+++ b/oauth/views.py
@@ -1,13 +1,159 @@
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 WBOauthManager, GoogleOauthManager, get_manager_by_type
+
+
+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)
+ if not rsp:
+ return HttpResponseRedirect(manager.get_authorization_url())
+ user = manager.get_oauth_userinfo()
+
+ if user:
+ try:
+ user = OAuthUser.objects.get(type=type, openid=user.openid)
+ except ObjectDoesNotExist:
+ pass
+ email = user.email
+ if email:
+ author = None
+ try:
+ author = get_user_model().objects.get(email=email)
+ except ObjectDoesNotExist:
+ pass
+ if not author:
+ author = get_user_model(). \
+ objects.create_user(username=user.nikename + '_' + str(user.openid), email=email)
+ user.author = author
+ user.save()
+ login(request, author)
+ return HttpResponseRedirect('/')
+ if not email:
+ # todo
+ # 未避免用户名重复,暂时使用oauth用户名+openid这种方式来创建用户
+ author = get_user_model().objects.get_or_create(username=user.nikename + '_' + str(user.openid))[0]
+ user.author = author
+ user.save()
+
+ url = reverse('oauth:require_email', kwargs=
+ {
+ 'oauthid': user.id
+ })
+ print(url)
+ return HttpResponseRedirect(url)
+
+
+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 = get_user_model().objects.get(pk=oauthuser.author_id)
+ if oauthuser.email and author.email:
+ login(request, author)
+ return HttpResponseRedirect('/')
+ author.set_password('$%^Q1W2E3R4T5Y6,./')
+ author.email = oauthuser.email
+ author.save()
+ login(request, author)
+
+ site = Site.objects.get_current().domain
+ send_email('恭喜您绑定成功!', '''
+ 恭喜您,您已经成功绑定您的邮箱,您可以使用{type}来直接免密码登录本网站.欢迎您继续关注本站,地址是
+
+ {url}
+ 再次感谢您!
+
+ 如果上面链接无法打开,请将此链接复制至浏览器。
+ {url}
+ '''.format(type=oauthuser.type, url='http://' + site), [oauthuser.email, ])
+ return HttpResponseRedirect('/')
+
+
+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)
+ print(url)
+ content = """
+ 请点击下面链接绑定您的邮箱
+
+ {url}
+
+ 再次感谢您!
+
+ 如果上面链接无法打开,请将此链接复制至浏览器。
+ {url}
+ """.format(url=url)
+ send_email('绑定您的电子邮箱', content, [email, ])
+ return HttpResponseRedirect('/')
+
+
+"""
def wbauthorize(request, sitename):
manager = WBOauthManager(client_id=settings.OAHUTH['sina']['appkey'],
client_secret=settings.OAHUTH['sina']['appsecret'],
@@ -27,33 +173,34 @@ def wboauthurl(request):
def googleoauthurl(request):
- manager = GoogleOauthManager(client_id=settings.OAHUTH['google']['appkey'],
- client_secret=settings.OAHUTH['google']['appsecret'],
- callback_url=settings.OAHUTH['google']['callbackurl'])
+ manager = GoogleOauthManager()
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'])
+ manager = GoogleOauthManager()
code = request.GET.get('code', None)
rsp = manager.get_access_token_by_code(code)
- print(rsp)
+ if not rsp:
+ return HttpResponseRedirect(manager.get_authorization_url())
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 email:
+ author = get_user_model().objects.get(email=email)
+ if not author:
+ author = get_user_model().objects.create_user(username=user["name"], email=email)
+ 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()
+ login(request, author)
+ else:
+ pass
+ return HttpResponseRedirect('/')
+"""
diff --git a/templates/account/login.html b/templates/account/login.html
index 10800ba..57c55a3 100644
--- a/templates/account/login.html
+++ b/templates/account/login.html
@@ -28,7 +28,8 @@
Stay signed in
-
+ {% load oauth_tags %}
+ {% load_oauth_applications %}
diff --git a/templates/blog/article_detail.html b/templates/blog/article_detail.html
index 653eaa3..65049b2 100755
--- a/templates/blog/article_detail.html
+++ b/templates/blog/article_detail.html
@@ -45,7 +45,17 @@
{% 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 %}
+
+ {% endif %}
{% endif %}
diff --git a/templates/oauth/oauth_applications.html b/templates/oauth/oauth_applications.html
new file mode 100644
index 0000000..7b83c8f
--- /dev/null
+++ b/templates/oauth/oauth_applications.html
@@ -0,0 +1,10 @@
+
diff --git a/templates/oauth/require_email.html b/templates/oauth/require_email.html
new file mode 100644
index 0000000..a8ebb95
--- /dev/null
+++ b/templates/oauth/require_email.html
@@ -0,0 +1,46 @@
+{% extends 'share_layout/base_account.html' %}
+
+{% load static %}
+{% block content %}
+
+
+
Binding E-mail account
+
+
+ {% if picture %}
+

+ {% else %}
+

+ {% endif %}
+
+
+
+
+ Sign In
+
+
+
+{% endblock %}
\ No newline at end of file
diff --git a/templates/share_layout/base.html b/templates/share_layout/base.html
index 7eff3d5..0d19bf6 100644
--- a/templates/share_layout/base.html
+++ b/templates/share_layout/base.html
@@ -17,7 +17,7 @@
{% block header %}
{% endblock %}
- {% comment %}{% endcomment %}
+
@@ -32,11 +32,13 @@
{% compress css %}
+
{% comment %}{% endcomment %}
+
{% block compress_css %}
{% endblock %}
{% endcompress %}
@@ -44,6 +46,7 @@
{% endcomment %}
{% compress js %}
+
{% endcompress %}
diff --git a/templates/share_layout/base_account.html b/templates/share_layout/base_account.html
index d95a1ad..6457260 100644
--- a/templates/share_layout/base_account.html
+++ b/templates/share_layout/base_account.html
@@ -15,7 +15,7 @@
{% compress css %}
-
+
您还没有登录,请您登录后发表评论。
+ + {% load oauth_tags %} + {% load_oauth_applications %} + +