diff --git a/README.md b/README.md new file mode 100644 index 0000000..abcdd57 --- /dev/null +++ b/README.md @@ -0,0 +1,2 @@ +# software_DjangoBlog + diff --git a/doc/实践考评-开源软件大作业项目的自评报告(1).xlsx b/doc/实践考评-开源软件大作业项目的自评报告(1).xlsx deleted file mode 100644 index 191f90d..0000000 Binary files a/doc/实践考评-开源软件大作业项目的自评报告(1).xlsx and /dev/null differ diff --git a/src/.gitattributes b/src/.gitattributes deleted file mode 100644 index fd52ece..0000000 --- a/src/.gitattributes +++ /dev/null @@ -1,6 +0,0 @@ -blog/static/* linguist-vendored -*.js linguist-vendored -*.css linguist-vendored -* text=auto -*.sh text eol=lf -*.conf text eol=lf \ No newline at end of file diff --git a/src/.gitignore b/src/.gitignore deleted file mode 100644 index 69797c5..0000000 --- a/src/.gitignore +++ /dev/null @@ -1,80 +0,0 @@ -# Byte-compiled / optimized / DLL files -__pycache__/ -*.py[cod] -*$py.class - -# C extensions -*.so - -# Distribution / packaging -.Python -env/ -build/ -develop-eggs/ -dist/ -downloads/ -eggs/ -.eggs/ -lib/ -lib64/ -parts/ -sdist/ -var/ -*.egg-info/ -.installed.cfg -*.egg - -# PyInstaller -# Usually these files are written by a python script from a template -# before PyInstaller builds the exe, so as to inject date/other infos into it. -*.manifest -*.spec - -# Installer logs -pip-log.txt -pip-delete-this-directory.txt - -# Unit test / coverage reports -htmlcov/ -.tox/ -.coverage -.coverage.* -.cache -nosetests.xml -coverage.xml -*,cover - -# Translations -*.pot - -# Django stuff: -*.log -logs/ - -# Sphinx documentation -docs/_build/ - -# PyBuilder -target/ - - -# PyCharm -# http://www.jetbrains.com/pycharm/webhelp/project.html -.idea -.iml -# virtualenv -venv/ - -collectedstatic/ -djangoblog/whoosh_index/ -google93fd32dbd906620a.html -baidu_verify_FlHL7cUyC9.html -BingSiteAuth.xml -cb9339dbe2ff86a5aa169d28dba5f615.txt -werobot_session.* -django.jpg -uploads/ -settings_production.py -werobot_session.db -bin/datas/ -myenv/ \ No newline at end of file diff --git a/src/DjangoBlog b/src/DjangoBlog deleted file mode 160000 index ef67f8d..0000000 --- a/src/DjangoBlog +++ /dev/null @@ -1 +0,0 @@ -Subproject commit ef67f8db4fafce7e84c0e7bae23d5c5bf8869fac diff --git a/src/DjangoBlog/.coveragerc b/src/DjangoBlog/.coveragerc new file mode 100644 index 0000000..9757484 --- /dev/null +++ b/src/DjangoBlog/.coveragerc @@ -0,0 +1,10 @@ +[run] +source = . +include = *.py +omit = + *migrations* + *tests* + *.html + *whoosh_cn_backend* + *settings.py* + *venv* diff --git a/src/.dockerignore b/src/DjangoBlog/.dockerignore similarity index 87% rename from src/.dockerignore rename to src/DjangoBlog/.dockerignore index bd68a58..2818c38 100644 --- a/src/.dockerignore +++ b/src/DjangoBlog/.dockerignore @@ -8,5 +8,4 @@ settings_production.py *.md docs/ logs/ -static/ -.github/ +static/ \ No newline at end of file diff --git a/.idea/.gitignore b/src/DjangoBlog/.idea/.gitignore similarity index 100% rename from .idea/.gitignore rename to src/DjangoBlog/.idea/.gitignore diff --git a/.idea/DjangoBlog.iml b/src/DjangoBlog/.idea/DjangoBlog.iml similarity index 59% rename from .idea/DjangoBlog.iml rename to src/DjangoBlog/.idea/DjangoBlog.iml index 829c823..0ea9148 100644 --- a/.idea/DjangoBlog.iml +++ b/src/DjangoBlog/.idea/DjangoBlog.iml @@ -3,9 +3,9 @@ - - + - + @@ -24,14 +24,11 @@ - - - \ No newline at end of file diff --git a/src/DjangoBlog/.idea/dataSources.xml b/src/DjangoBlog/.idea/dataSources.xml new file mode 100644 index 0000000..b1ae6d6 --- /dev/null +++ b/src/DjangoBlog/.idea/dataSources.xml @@ -0,0 +1,12 @@ + + + + + mysql.8 + true + com.mysql.cj.jdbc.Driver + jdbc:mysql://localhost:3306 + $ProjectFileDir$ + + + \ No newline at end of file diff --git a/.idea/inspectionProfiles/profiles_settings.xml b/src/DjangoBlog/.idea/inspectionProfiles/profiles_settings.xml similarity index 100% rename from .idea/inspectionProfiles/profiles_settings.xml rename to src/DjangoBlog/.idea/inspectionProfiles/profiles_settings.xml diff --git a/.idea/misc.xml b/src/DjangoBlog/.idea/misc.xml similarity index 62% rename from .idea/misc.xml rename to src/DjangoBlog/.idea/misc.xml index 913e951..9660774 100644 --- a/.idea/misc.xml +++ b/src/DjangoBlog/.idea/misc.xml @@ -1,4 +1,4 @@ - + \ No newline at end of file diff --git a/.idea/modules.xml b/src/DjangoBlog/.idea/modules.xml similarity index 100% rename from .idea/modules.xml rename to src/DjangoBlog/.idea/modules.xml diff --git a/.idea/vcs.xml b/src/DjangoBlog/.idea/vcs.xml similarity index 69% rename from .idea/vcs.xml rename to src/DjangoBlog/.idea/vcs.xml index 35eb1dd..8fe5bdb 100644 --- a/.idea/vcs.xml +++ b/src/DjangoBlog/.idea/vcs.xml @@ -2,5 +2,6 @@ + \ No newline at end of file diff --git a/src/Dockerfile b/src/DjangoBlog/Dockerfile similarity index 100% rename from src/Dockerfile rename to src/DjangoBlog/Dockerfile diff --git a/src/LICENSE b/src/DjangoBlog/LICENSE similarity index 100% rename from src/LICENSE rename to src/DjangoBlog/LICENSE diff --git a/src/README.md b/src/DjangoBlog/README.md similarity index 100% rename from src/README.md rename to src/DjangoBlog/README.md diff --git a/src/accounts/__init__.py b/src/DjangoBlog/accounts/__init__.py similarity index 100% rename from src/accounts/__init__.py rename to src/DjangoBlog/accounts/__init__.py diff --git a/src/DjangoBlog/accounts/__pycache__/__init__.cpython-310.pyc b/src/DjangoBlog/accounts/__pycache__/__init__.cpython-310.pyc new file mode 100644 index 0000000..72a6993 Binary files /dev/null and b/src/DjangoBlog/accounts/__pycache__/__init__.cpython-310.pyc differ diff --git a/src/DjangoBlog/accounts/__pycache__/admin.cpython-310.pyc b/src/DjangoBlog/accounts/__pycache__/admin.cpython-310.pyc new file mode 100644 index 0000000..edc5ba8 Binary files /dev/null and b/src/DjangoBlog/accounts/__pycache__/admin.cpython-310.pyc differ diff --git a/src/DjangoBlog/accounts/__pycache__/apps.cpython-310.pyc b/src/DjangoBlog/accounts/__pycache__/apps.cpython-310.pyc new file mode 100644 index 0000000..205e567 Binary files /dev/null and b/src/DjangoBlog/accounts/__pycache__/apps.cpython-310.pyc differ diff --git a/src/DjangoBlog/accounts/__pycache__/forms.cpython-310.pyc b/src/DjangoBlog/accounts/__pycache__/forms.cpython-310.pyc new file mode 100644 index 0000000..fbbc699 Binary files /dev/null and b/src/DjangoBlog/accounts/__pycache__/forms.cpython-310.pyc differ diff --git a/src/DjangoBlog/accounts/__pycache__/models.cpython-310.pyc b/src/DjangoBlog/accounts/__pycache__/models.cpython-310.pyc new file mode 100644 index 0000000..49ee56f Binary files /dev/null and b/src/DjangoBlog/accounts/__pycache__/models.cpython-310.pyc differ diff --git a/src/DjangoBlog/accounts/__pycache__/urls.cpython-310.pyc b/src/DjangoBlog/accounts/__pycache__/urls.cpython-310.pyc new file mode 100644 index 0000000..3c973a9 Binary files /dev/null and b/src/DjangoBlog/accounts/__pycache__/urls.cpython-310.pyc differ diff --git a/src/DjangoBlog/accounts/__pycache__/user_login_backend.cpython-310.pyc b/src/DjangoBlog/accounts/__pycache__/user_login_backend.cpython-310.pyc new file mode 100644 index 0000000..d2a5e0c Binary files /dev/null and b/src/DjangoBlog/accounts/__pycache__/user_login_backend.cpython-310.pyc differ diff --git a/src/DjangoBlog/accounts/__pycache__/utils.cpython-310.pyc b/src/DjangoBlog/accounts/__pycache__/utils.cpython-310.pyc new file mode 100644 index 0000000..a8fcf48 Binary files /dev/null and b/src/DjangoBlog/accounts/__pycache__/utils.cpython-310.pyc differ diff --git a/src/DjangoBlog/accounts/__pycache__/views.cpython-310.pyc b/src/DjangoBlog/accounts/__pycache__/views.cpython-310.pyc new file mode 100644 index 0000000..19e7d71 Binary files /dev/null and b/src/DjangoBlog/accounts/__pycache__/views.cpython-310.pyc differ diff --git a/src/DjangoBlog/accounts/admin.py b/src/DjangoBlog/accounts/admin.py new file mode 100644 index 0000000..32e483c --- /dev/null +++ b/src/DjangoBlog/accounts/admin.py @@ -0,0 +1,59 @@ +from django import forms +from django.contrib.auth.admin import UserAdmin +from django.contrib.auth.forms import UserChangeForm +from django.contrib.auth.forms import UsernameField +from django.utils.translation import gettext_lazy as _ + +# Register your models here. +from .models import BlogUser + + +class BlogUserCreationForm(forms.ModelForm): + password1 = forms.CharField(label=_('password'), widget=forms.PasswordInput) + password2 = forms.CharField(label=_('Enter password again'), widget=forms.PasswordInput) + + class Meta: + model = BlogUser + fields = ('email',) + + def clean_password2(self): + # Check that the two password entries match + password1 = self.cleaned_data.get("password1") + password2 = self.cleaned_data.get("password2") + if password1 and password2 and password1 != password2: + raise forms.ValidationError(_("passwords do not match")) + return password2 + + def save(self, commit=True): + # Save the provided password in hashed format + user = super().save(commit=False) + user.set_password(self.cleaned_data["password1"]) + if commit: + user.source = 'adminsite' + user.save() + return user + + +class BlogUserChangeForm(UserChangeForm): + class Meta: + model = BlogUser + fields = '__all__' + field_classes = {'username': UsernameField} + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + + +class BlogUserAdmin(UserAdmin): + form = BlogUserChangeForm + add_form = BlogUserCreationForm + list_display = ( + 'id', + 'nickname', + 'username', + 'email', + 'last_login', + 'date_joined', + 'source') + list_display_links = ('id', 'username') + ordering = ('-id',) diff --git a/src/accounts/apps.py b/src/DjangoBlog/accounts/apps.py similarity index 100% rename from src/accounts/apps.py rename to src/DjangoBlog/accounts/apps.py diff --git a/src/accounts/forms.py b/src/DjangoBlog/accounts/forms.py similarity index 100% rename from src/accounts/forms.py rename to src/DjangoBlog/accounts/forms.py diff --git a/src/accounts/migrations/0001_initial.py b/src/DjangoBlog/accounts/migrations/0001_initial.py similarity index 100% rename from src/accounts/migrations/0001_initial.py rename to src/DjangoBlog/accounts/migrations/0001_initial.py diff --git a/src/accounts/migrations/0002_alter_bloguser_options_remove_bloguser_created_time_and_more.py b/src/DjangoBlog/accounts/migrations/0002_alter_bloguser_options_remove_bloguser_created_time_and_more.py similarity index 100% rename from src/accounts/migrations/0002_alter_bloguser_options_remove_bloguser_created_time_and_more.py rename to src/DjangoBlog/accounts/migrations/0002_alter_bloguser_options_remove_bloguser_created_time_and_more.py diff --git a/src/accounts/migrations/__init__.py b/src/DjangoBlog/accounts/migrations/__init__.py similarity index 100% rename from src/accounts/migrations/__init__.py rename to src/DjangoBlog/accounts/migrations/__init__.py diff --git a/src/DjangoBlog/accounts/migrations/__pycache__/0001_initial.cpython-310.pyc b/src/DjangoBlog/accounts/migrations/__pycache__/0001_initial.cpython-310.pyc new file mode 100644 index 0000000..e9ca194 Binary files /dev/null and b/src/DjangoBlog/accounts/migrations/__pycache__/0001_initial.cpython-310.pyc differ diff --git a/src/DjangoBlog/accounts/migrations/__pycache__/0002_alter_bloguser_options_remove_bloguser_created_time_and_more.cpython-310.pyc b/src/DjangoBlog/accounts/migrations/__pycache__/0002_alter_bloguser_options_remove_bloguser_created_time_and_more.cpython-310.pyc new file mode 100644 index 0000000..4c095b2 Binary files /dev/null and b/src/DjangoBlog/accounts/migrations/__pycache__/0002_alter_bloguser_options_remove_bloguser_created_time_and_more.cpython-310.pyc differ diff --git a/src/DjangoBlog/accounts/migrations/__pycache__/__init__.cpython-310.pyc b/src/DjangoBlog/accounts/migrations/__pycache__/__init__.cpython-310.pyc new file mode 100644 index 0000000..22591be Binary files /dev/null and b/src/DjangoBlog/accounts/migrations/__pycache__/__init__.cpython-310.pyc differ diff --git a/src/DjangoBlog/accounts/models.py b/src/DjangoBlog/accounts/models.py new file mode 100644 index 0000000..3baddbb --- /dev/null +++ b/src/DjangoBlog/accounts/models.py @@ -0,0 +1,35 @@ +from django.contrib.auth.models import AbstractUser +from django.db import models +from django.urls import reverse +from django.utils.timezone import now +from django.utils.translation import gettext_lazy as _ +from djangoblog.utils import get_current_site + + +# Create your models here. + +class BlogUser(AbstractUser): + nickname = models.CharField(_('nick name'), max_length=100, blank=True) + creation_time = models.DateTimeField(_('creation time'), default=now) + last_modify_time = models.DateTimeField(_('last modify time'), default=now) + source = models.CharField(_('create source'), max_length=100, blank=True) + + def get_absolute_url(self): + return reverse( + 'blog:author_detail', kwargs={ + 'author_name': self.username}) + + def __str__(self): + return self.email + + def get_full_url(self): + site = get_current_site().domain + url = "https://{site}{path}".format(site=site, + path=self.get_absolute_url()) + return url + + class Meta: + ordering = ['-id'] + verbose_name = _('user') + verbose_name_plural = verbose_name + get_latest_by = 'id' diff --git a/src/accounts/templatetags/__init__.py b/src/DjangoBlog/accounts/templatetags/__init__.py similarity index 100% rename from src/accounts/templatetags/__init__.py rename to src/DjangoBlog/accounts/templatetags/__init__.py diff --git a/src/DjangoBlog/accounts/templatetags/__pycache__/__init__.cpython-310.pyc b/src/DjangoBlog/accounts/templatetags/__pycache__/__init__.cpython-310.pyc new file mode 100644 index 0000000..50e4d17 Binary files /dev/null and b/src/DjangoBlog/accounts/templatetags/__pycache__/__init__.cpython-310.pyc differ diff --git a/src/accounts/tests.py b/src/DjangoBlog/accounts/tests.py similarity index 100% rename from src/accounts/tests.py rename to src/DjangoBlog/accounts/tests.py diff --git a/src/accounts/urls.py b/src/DjangoBlog/accounts/urls.py similarity index 100% rename from src/accounts/urls.py rename to src/DjangoBlog/accounts/urls.py diff --git a/src/DjangoBlog/accounts/user_login_backend.py b/src/DjangoBlog/accounts/user_login_backend.py new file mode 100644 index 0000000..73cdca1 --- /dev/null +++ b/src/DjangoBlog/accounts/user_login_backend.py @@ -0,0 +1,26 @@ +from django.contrib.auth import get_user_model +from django.contrib.auth.backends import ModelBackend + + +class EmailOrUsernameModelBackend(ModelBackend): + """ + 允许使用用户名或邮箱登录 + """ + + def authenticate(self, request, username=None, password=None, **kwargs): + if '@' in username: + kwargs = {'email': username} + else: + kwargs = {'username': username} + try: + user = get_user_model().objects.get(**kwargs) + if user.check_password(password): + return user + except get_user_model().DoesNotExist: + return None + + def get_user(self, username): + try: + return get_user_model().objects.get(pk=username) + except get_user_model().DoesNotExist: + return None diff --git a/src/accounts/utils.py b/src/DjangoBlog/accounts/utils.py similarity index 100% rename from src/accounts/utils.py rename to src/DjangoBlog/accounts/utils.py diff --git a/src/accounts/views.py b/src/DjangoBlog/accounts/views.py similarity index 69% rename from src/accounts/views.py rename to src/DjangoBlog/accounts/views.py index e7a675e..ae67aec 100644 --- a/src/accounts/views.py +++ b/src/DjangoBlog/accounts/views.py @@ -1,7 +1,3 @@ -# 模块说明: -# 该模块定义了与用户账户相关的视图,包括注册、登录、注销、忘记密码等功能。 -# 使用 Django 的类视图和表单视图来处理用户请求。 - import logging from django.utils.translation import gettext_lazy as _ from django.conf import settings @@ -32,24 +28,23 @@ from .models import BlogUser logger = logging.getLogger(__name__) -# 注册视图 + +# Create your views here. + class RegisterView(FormView): - # 使用注册表单和模板 form_class = RegisterForm template_name = 'account/registration_form.html' @method_decorator(csrf_protect) def dispatch(self, *args, **kwargs): - # 确保请求受 CSRF 保护 return super(RegisterView, self).dispatch(*args, **kwargs) def form_valid(self, form): - # 表单验证通过后处理注册逻辑 if form.is_valid(): - user = form.save(False) # 创建用户但不保存到数据库 - user.is_active = False # 设置用户为未激活状态 - user.source = 'Register' # 标记用户来源 - user.save(True) # 保存用户到数据库 + user = form.save(False) + user.is_active = False + user.source = 'Register' + user.save(True) site = get_current_site().domain sign = get_sha256(get_sha256(settings.SECRET_KEY + str(user.id))) @@ -75,74 +70,82 @@ class RegisterView(FormView): ], title='验证您的电子邮箱', content=content) - # 重定向到结果页面 - url = reverse('accounts:result') + '?type=register&id=' + str(user.id) + + url = reverse('accounts:result') + \ + '?type=register&id=' + str(user.id) return HttpResponseRedirect(url) else: - # 如果表单无效,重新渲染表单 - return self.render_to_response({'form': form}) + return self.render_to_response({ + 'form': form + }) + -# 注销视图 class LogoutView(RedirectView): - url = '/login/' # 注销后重定向到登录页面 + url = '/login/' @method_decorator(never_cache) def dispatch(self, request, *args, **kwargs): - # 确保注销后页面不被缓存 return super(LogoutView, self).dispatch(request, *args, **kwargs) def get(self, request, *args, **kwargs): - logout(request) # 执行注销操作 - delete_sidebar_cache() # 清除侧边栏缓存 + logout(request) + delete_sidebar_cache() return super(LogoutView, self).get(request, *args, **kwargs) -# 登录视图 + class LoginView(FormView): - # 使用登录表单和模板 form_class = LoginForm template_name = 'account/login.html' - success_url = '/' # 登录成功后重定向的默认地址 + success_url = '/' redirect_field_name = REDIRECT_FIELD_NAME - login_ttl = 2626560 # 设置会话过期时间为一个月 + login_ttl = 2626560 # 一个月的时间 @method_decorator(sensitive_post_parameters('password')) @method_decorator(csrf_protect) @method_decorator(never_cache) def dispatch(self, request, *args, **kwargs): - # 确保密码字段敏感、请求受 CSRF 保护且页面不被缓存 + return super(LoginView, self).dispatch(request, *args, **kwargs) def get_context_data(self, **kwargs): - # 获取上下文数据,添加重定向地址 - redirect_to = self.request.GET.get(self.redirect_field_name, '/') + 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(): - delete_sidebar_cache() # 清除侧边栏缓存 - auth.login(self.request, form.get_user()) # 执行登录操作 + delete_sidebar_cache() + logger.info(self.redirect_field_name) + + auth.login(self.request, form.get_user()) if self.request.POST.get("remember"): - self.request.session.set_expiry(self.login_ttl) # 设置会话过期时间 + self.request.session.set_expiry(self.login_ttl) return super(LoginView, self).form_valid(form) + # return HttpResponseRedirect('/') else: - # 如果表单无效,重新渲染表单 - return self.render_to_response({'form': form}) + return self.render_to_response({ + 'form': form + }) def get_success_url(self): - # 获取登录成功后的重定向地址 - redirect_to = self.request.POST.get(self.redirect_field_name, self.success_url) - if not url_has_allowed_host_and_scheme(url=redirect_to, allowed_hosts=[self.request.get_host()]): + + redirect_to = self.request.POST.get(self.redirect_field_name) + if not url_has_allowed_host_and_scheme( + url=redirect_to, allowed_hosts=[ + self.request.get_host()]): redirect_to = self.success_url return redirect_to -# 账户结果视图 + def account_result(request): - # 处理注册或验证结果 type = request.GET.get('type') id = request.GET.get('id') + user = get_object_or_404(get_user_model(), id=id) logger.info(type) if user.is_active: @@ -157,8 +160,8 @@ def account_result(request): c_sign = get_sha256(get_sha256(settings.SECRET_KEY + str(user.id))) sign = request.GET.get('sign') if sign != c_sign: - return HttpResponseForbidden() # 验证失败返回 403 - user.is_active = True # 激活用户 + return HttpResponseForbidden() + user.is_active = True user.save() content = ''' 恭喜您已经成功的完成邮箱验证,您现在可以使用您的账号来登录本站。 @@ -171,30 +174,31 @@ def account_result(request): else: return HttpResponseRedirect('/') -# 忘记密码视图 + class ForgetPasswordView(FormView): form_class = ForgetPasswordForm template_name = 'account/forget_password.html' def form_valid(self, form): - # 表单验证通过后处理密码重置逻辑 if form.is_valid(): blog_user = BlogUser.objects.filter(email=form.cleaned_data.get("email")).get() - blog_user.password = make_password(form.cleaned_data["new_password2"]) # 设置新密码 + blog_user.password = make_password(form.cleaned_data["new_password2"]) blog_user.save() - return HttpResponseRedirect('/login/') # 重定向到登录页面 + return HttpResponseRedirect('/login/') else: return self.render_to_response({'form': form}) -# 忘记密码验证码视图 + class ForgetPasswordEmailCode(View): + def post(self, request: HttpRequest): - # 处理验证码发送请求 form = ForgetPasswordCodeForm(request.POST) if not form.is_valid(): return HttpResponse("错误的邮箱") to_email = form.cleaned_data["email"] - code = generate_code() # 生成验证码 - utils.send_verify_email(to_email, code) # 发送验证码邮件 - utils.set_code(to_email, code) # 缓存验证码 + + code = generate_code() + utils.send_verify_email(to_email, code) + utils.set_code(to_email, code) + return HttpResponse("ok") diff --git a/src/blog/__init__.py b/src/DjangoBlog/blog/__init__.py similarity index 100% rename from src/blog/__init__.py rename to src/DjangoBlog/blog/__init__.py diff --git a/src/DjangoBlog/blog/__pycache__/__init__.cpython-310.pyc b/src/DjangoBlog/blog/__pycache__/__init__.cpython-310.pyc new file mode 100644 index 0000000..282b443 Binary files /dev/null and b/src/DjangoBlog/blog/__pycache__/__init__.cpython-310.pyc differ diff --git a/src/DjangoBlog/blog/__pycache__/admin.cpython-310.pyc b/src/DjangoBlog/blog/__pycache__/admin.cpython-310.pyc new file mode 100644 index 0000000..478ab3c Binary files /dev/null and b/src/DjangoBlog/blog/__pycache__/admin.cpython-310.pyc differ diff --git a/src/DjangoBlog/blog/__pycache__/apps.cpython-310.pyc b/src/DjangoBlog/blog/__pycache__/apps.cpython-310.pyc new file mode 100644 index 0000000..d6140b2 Binary files /dev/null and b/src/DjangoBlog/blog/__pycache__/apps.cpython-310.pyc differ diff --git a/src/DjangoBlog/blog/__pycache__/context_processors.cpython-310.pyc b/src/DjangoBlog/blog/__pycache__/context_processors.cpython-310.pyc new file mode 100644 index 0000000..afe3da2 Binary files /dev/null and b/src/DjangoBlog/blog/__pycache__/context_processors.cpython-310.pyc differ diff --git a/src/DjangoBlog/blog/__pycache__/documents.cpython-310.pyc b/src/DjangoBlog/blog/__pycache__/documents.cpython-310.pyc new file mode 100644 index 0000000..e8b1aec Binary files /dev/null and b/src/DjangoBlog/blog/__pycache__/documents.cpython-310.pyc differ diff --git a/src/DjangoBlog/blog/__pycache__/middleware.cpython-310.pyc b/src/DjangoBlog/blog/__pycache__/middleware.cpython-310.pyc new file mode 100644 index 0000000..99f69fb Binary files /dev/null and b/src/DjangoBlog/blog/__pycache__/middleware.cpython-310.pyc differ diff --git a/src/DjangoBlog/blog/__pycache__/models.cpython-310.pyc b/src/DjangoBlog/blog/__pycache__/models.cpython-310.pyc new file mode 100644 index 0000000..a95b171 Binary files /dev/null and b/src/DjangoBlog/blog/__pycache__/models.cpython-310.pyc differ diff --git a/src/DjangoBlog/blog/__pycache__/search_indexes.cpython-310.pyc b/src/DjangoBlog/blog/__pycache__/search_indexes.cpython-310.pyc new file mode 100644 index 0000000..b0b14a6 Binary files /dev/null and b/src/DjangoBlog/blog/__pycache__/search_indexes.cpython-310.pyc differ diff --git a/src/DjangoBlog/blog/__pycache__/urls.cpython-310.pyc b/src/DjangoBlog/blog/__pycache__/urls.cpython-310.pyc new file mode 100644 index 0000000..42965cb Binary files /dev/null and b/src/DjangoBlog/blog/__pycache__/urls.cpython-310.pyc differ diff --git a/src/DjangoBlog/blog/__pycache__/views.cpython-310.pyc b/src/DjangoBlog/blog/__pycache__/views.cpython-310.pyc new file mode 100644 index 0000000..c085e73 Binary files /dev/null and b/src/DjangoBlog/blog/__pycache__/views.cpython-310.pyc differ diff --git a/src/DjangoBlog/blog/admin.py b/src/DjangoBlog/blog/admin.py new file mode 100644 index 0000000..0396fc2 --- /dev/null +++ b/src/DjangoBlog/blog/admin.py @@ -0,0 +1,125 @@ +from django import forms +from django.contrib import admin +from django.contrib.auth import get_user_model +from django.urls import reverse +from django.utils.html import format_html +from django.utils.translation import gettext_lazy as _ + +# 注册你的模型到admin后台 +# Register your models here. +from .models import Article + + +class ArticleForm(forms.ModelForm): + # 文章表单类,可在这里自定义文章模型对应的表单字段等,当前示例中暂时没有额外自定义字段(原body字段的AdminPagedownWidget注释掉了) + class Meta: + model = Article # 关联的模型是Article + fields = '__all__' # 表单包含模型的所有字段 + + +# 定义将选中文章发布的动作函数 +def makr_article_publish(modeladmin, request, queryset): + queryset.update(status='p') # 将选中文章的状态更新为'p'(发布状态) + + +# 定义将选中文章设为草稿的动作函数 +def draft_article(modeladmin, request, queryset): + queryset.update(status='d') # 将选中文章的状态更新为'd'(草稿状态) + + +# 定义关闭文章评论的动作函数 +def close_article_commentstatus(modeladmin, request, queryset): + queryset.update(comment_status='c') # 将选中文章的评论状态更新为'c'(关闭状态) + + +# 定义开启文章评论的动作函数 +def open_article_commentstatus(modeladmin, request, queryset): + queryset.update(comment_status='o') # 将选中文章的评论状态更新为'o'(开启状态) + + +# 为各个动作函数设置在admin界面显示的简短描述 +makr_article_publish.short_description = _('Publish selected articles') +draft_article.short_description = _('Draft selected articles') +close_article_commentstatus.short_description = _('Close article comments') +open_article_commentstatus.short_description = _('Open article comments') + + +class ArticlelAdmin(admin.ModelAdmin): + list_per_page = 20 # 管理员列表页每页显示20条记录 + search_fields = ('body', 'title') # 可搜索的字段,按文章内容和标题搜索 + form = ArticleForm # 使用自定义的ArticleForm作为表单 + # 列表页显示的字段 + list_display = ( + 'id', + 'title', + 'author', + 'link_to_category', # 自定义的显示分类链接的字段 + 'creation_time', + 'views', + 'status', + 'type', + 'article_order') + list_display_links = ('id', 'title') # 列表页中可点击跳转详情的字段 + list_filter = ('status', 'type', 'category') # 可用于筛选的字段 + filter_horizontal = ('tags',) # 水平过滤选择的字段(用于多对多关系,如文章和标签) + exclude = ('creation_time', 'last_modify_time') # 编辑页排除的字段(不显示这两个时间字段) + view_on_site = True # 开启在站点查看文章的功能 + actions = [ + # 配置admin界面的动作选项,即上面定义的几个动作函数 + makr_article_publish, + draft_article, + close_article_commentstatus, + open_article_commentstatus] + + # 自定义显示分类为可点击链接的方法 + def link_to_category(self, obj): + # 获取分类模型的app_label和model_name,用于生成admin后台分类编辑页面的链接 + info = (obj.category._meta.app_label, obj.category._meta.model_name) + link = reverse('admin:%s_%s_change' % info, args=(obj.category.id,)) + # 格式化生成带链接的分类名称 + return format_html(u'%s' % (link, obj.category.name)) + + link_to_category.short_description = _('category') # 该自定义字段在列表页的显示名称 + + # 重写获取表单的方法,用于自定义作者字段的查询集 + 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) + + # 重写获取站点查看链接的方法,返回文章在站点的完整URL + def get_view_on_site_url(self, obj=None): + if obj: + url = obj.get_full_url() + return url + else: + from djangoblog.utils import get_current_site + site = get_current_site().domain + return site + + +class TagAdmin(admin.ModelAdmin): + exclude = ('slug', 'last_mod_time', 'creation_time') # 标签编辑页排除的字段 + + +class CategoryAdmin(admin.ModelAdmin): + list_display = ('name', 'parent_category', 'index') # 分类列表页显示的字段 + exclude = ('slug', 'last_mod_time', 'creation_time') # 分类编辑页排除的字段 + + +class LinksAdmin(admin.ModelAdmin): + exclude = ('last_mod_time', 'creation_time') # 链接编辑页排除的字段 + + +class SideBarAdmin(admin.ModelAdmin): + list_display = ('name', 'content', 'is_enable', 'sequence') # 侧边栏列表页显示的字段 + exclude = ('last_mod_time', 'creation_time') # 侧边栏编辑页排除的字段 + + +class BlogSettingsAdmin(admin.ModelAdmin): + pass # 博客设置的admin类,暂时没有额外配置 \ No newline at end of file diff --git a/src/blog/apps.py b/src/DjangoBlog/blog/apps.py similarity index 100% rename from src/blog/apps.py rename to src/DjangoBlog/blog/apps.py diff --git a/src/blog/context_processors.py b/src/DjangoBlog/blog/context_processors.py similarity index 100% rename from src/blog/context_processors.py rename to src/DjangoBlog/blog/context_processors.py diff --git a/src/blog/documents.py b/src/DjangoBlog/blog/documents.py similarity index 85% rename from src/blog/documents.py rename to src/DjangoBlog/blog/documents.py index e0dba02..0f1db7b 100644 --- a/src/blog/documents.py +++ b/src/DjangoBlog/blog/documents.py @@ -7,11 +7,9 @@ from elasticsearch_dsl.connections import connections from blog.models import Article -# 检查是否启用了 Elasticsearch ELASTICSEARCH_ENABLED = hasattr(settings, 'ELASTICSEARCH_DSL') if ELASTICSEARCH_ENABLED: - # 创建 Elasticsearch 连接 connections.create_connection( hosts=[settings.ELASTICSEARCH_DSL['default']['hosts']]) from elasticsearch import Elasticsearch @@ -21,10 +19,8 @@ if ELASTICSEARCH_ENABLED: c = IngestClient(es) try: - # 检查是否存在名为 'geoip' 的 pipeline c.get_pipeline('geoip') except elasticsearch.exceptions.NotFoundError: - # 如果不存在,则创建 'geoip' pipeline c.put_pipeline('geoip', body='''{ "description" : "Add geoip info", "processors" : [ @@ -37,7 +33,6 @@ if ELASTICSEARCH_ENABLED: }''') -# 定义 GeoIP 信息的内部文档 class GeoIp(InnerDoc): continent_name = Keyword() country_iso_code = Keyword() @@ -45,25 +40,21 @@ class GeoIp(InnerDoc): location = GeoPoint() -# 定义用户代理浏览器信息的内部文档 class UserAgentBrowser(InnerDoc): Family = Keyword() Version = Keyword() -# 定义用户代理操作系统信息的内部文档 class UserAgentOS(UserAgentBrowser): pass -# 定义用户代理设备信息的内部文档 class UserAgentDevice(InnerDoc): Family = Keyword() Brand = Keyword() Model = Keyword() -# 定义用户代理信息的内部文档 class UserAgent(InnerDoc): browser = Object(UserAgentBrowser, required=False) os = Object(UserAgentOS, required=False) @@ -72,7 +63,6 @@ class UserAgent(InnerDoc): is_bot = Boolean() -# 定义性能日志的 Elasticsearch 文档 class ElapsedTimeDocument(Document): url = Keyword() time_taken = Long() @@ -82,7 +72,6 @@ class ElapsedTimeDocument(Document): useragent = Object(UserAgent, required=False) class Index: - # 定义索引名称和设置 name = 'performance' settings = { "number_of_shards": 1, @@ -93,11 +82,9 @@ class ElapsedTimeDocument(Document): doc_type = 'ElapsedTime' -# 定义性能日志文档的管理器 class ElaspedTimeDocumentManager: @staticmethod def build_index(): - # 创建 Elasticsearch 索引 from elasticsearch import Elasticsearch client = Elasticsearch(settings.ELASTICSEARCH_DSL['default']['hosts']) res = client.indices.exists(index="performance") @@ -106,14 +93,12 @@ class ElaspedTimeDocumentManager: @staticmethod def delete_index(): - # 删除 Elasticsearch 索引 from elasticsearch import Elasticsearch es = Elasticsearch(settings.ELASTICSEARCH_DSL['default']['hosts']) es.indices.delete(index='performance', ignore=[400, 404]) @staticmethod def create(url, time_taken, log_datetime, useragent, ip): - # 创建性能日志文档 ElaspedTimeDocumentManager.build_index() ua = UserAgent() ua.browser = UserAgentBrowser() @@ -145,7 +130,6 @@ class ElaspedTimeDocumentManager: doc.save(pipeline="geoip") -# 定义文章的 Elasticsearch 文档 class ArticleDocument(Document): body = Text(analyzer='ik_max_word', search_analyzer='ik_smart') title = Text(analyzer='ik_max_word', search_analyzer='ik_smart') @@ -170,7 +154,6 @@ class ArticleDocument(Document): article_order = Integer() class Index: - # 定义索引名称和设置 name = 'blog' settings = { "number_of_shards": 1, @@ -181,25 +164,20 @@ class ArticleDocument(Document): doc_type = 'Article' -# 定义文章文档的管理器 class ArticleDocumentManager(): def __init__(self): - # 初始化时创建索引 self.create_index() def create_index(self): - # 创建 Elasticsearch 索引 ArticleDocument.init() def delete_index(self): - # 删除 Elasticsearch 索引 from elasticsearch import Elasticsearch es = Elasticsearch(settings.ELASTICSEARCH_DSL['default']['hosts']) es.indices.delete(index='blog', ignore=[400, 404]) def convert_to_doc(self, articles): - # 将文章对象转换为 Elasticsearch 文档 return [ ArticleDocument( meta={ @@ -224,7 +202,6 @@ class ArticleDocumentManager(): article_order=article.article_order) for article in articles] def rebuild(self, articles=None): - # 重建索引并重新保存文档 ArticleDocument.init() articles = articles if articles else Article.objects.all() docs = self.convert_to_doc(articles) @@ -232,6 +209,5 @@ class ArticleDocumentManager(): doc.save() def update_docs(self, docs): - # 更新文档 for doc in docs: - doc.save() \ No newline at end of file + doc.save() diff --git a/src/blog/forms.py b/src/DjangoBlog/blog/forms.py similarity index 100% rename from src/blog/forms.py rename to src/DjangoBlog/blog/forms.py diff --git a/src/blog/management/__init__.py b/src/DjangoBlog/blog/management/__init__.py similarity index 100% rename from src/blog/management/__init__.py rename to src/DjangoBlog/blog/management/__init__.py diff --git a/src/DjangoBlog/blog/management/__pycache__/__init__.cpython-310.pyc b/src/DjangoBlog/blog/management/__pycache__/__init__.cpython-310.pyc new file mode 100644 index 0000000..786e0ac Binary files /dev/null and b/src/DjangoBlog/blog/management/__pycache__/__init__.cpython-310.pyc differ diff --git a/src/blog/management/commands/__init__.py b/src/DjangoBlog/blog/management/commands/__init__.py similarity index 100% rename from src/blog/management/commands/__init__.py rename to src/DjangoBlog/blog/management/commands/__init__.py diff --git a/src/DjangoBlog/blog/management/commands/__pycache__/__init__.cpython-310.pyc b/src/DjangoBlog/blog/management/commands/__pycache__/__init__.cpython-310.pyc new file mode 100644 index 0000000..25d8f20 Binary files /dev/null and b/src/DjangoBlog/blog/management/commands/__pycache__/__init__.cpython-310.pyc differ diff --git a/src/DjangoBlog/blog/management/commands/__pycache__/create_testdata.cpython-310.pyc b/src/DjangoBlog/blog/management/commands/__pycache__/create_testdata.cpython-310.pyc new file mode 100644 index 0000000..7a5d85a Binary files /dev/null and b/src/DjangoBlog/blog/management/commands/__pycache__/create_testdata.cpython-310.pyc differ diff --git a/src/blog/management/commands/build_index.py b/src/DjangoBlog/blog/management/commands/build_index.py similarity index 100% rename from src/blog/management/commands/build_index.py rename to src/DjangoBlog/blog/management/commands/build_index.py diff --git a/src/blog/management/commands/build_search_words.py b/src/DjangoBlog/blog/management/commands/build_search_words.py similarity index 100% rename from src/blog/management/commands/build_search_words.py rename to src/DjangoBlog/blog/management/commands/build_search_words.py diff --git a/src/blog/management/commands/clear_cache.py b/src/DjangoBlog/blog/management/commands/clear_cache.py similarity index 100% rename from src/blog/management/commands/clear_cache.py rename to src/DjangoBlog/blog/management/commands/clear_cache.py diff --git a/src/blog/management/commands/create_testdata.py b/src/DjangoBlog/blog/management/commands/create_testdata.py similarity index 100% rename from src/blog/management/commands/create_testdata.py rename to src/DjangoBlog/blog/management/commands/create_testdata.py diff --git a/src/blog/management/commands/ping_baidu.py b/src/DjangoBlog/blog/management/commands/ping_baidu.py similarity index 100% rename from src/blog/management/commands/ping_baidu.py rename to src/DjangoBlog/blog/management/commands/ping_baidu.py diff --git a/src/blog/management/commands/sync_user_avatar.py b/src/DjangoBlog/blog/management/commands/sync_user_avatar.py similarity index 100% rename from src/blog/management/commands/sync_user_avatar.py rename to src/DjangoBlog/blog/management/commands/sync_user_avatar.py diff --git a/src/DjangoBlog/blog/middleware.py b/src/DjangoBlog/blog/middleware.py new file mode 100644 index 0000000..94dd70c --- /dev/null +++ b/src/DjangoBlog/blog/middleware.py @@ -0,0 +1,42 @@ +import logging +import time + +from ipware import get_client_ip +from user_agents import parse + +from blog.documents import ELASTICSEARCH_ENABLED, ElaspedTimeDocumentManager + +logger = logging.getLogger(__name__) + + +class OnlineMiddleware(object): + def __init__(self, get_response=None): + self.get_response = get_response + super().__init__() + + def __call__(self, request): + ''' page render time ''' + start_time = time.time() + response = self.get_response(request) + http_user_agent = request.META.get('HTTP_USER_AGENT', '') + ip, _ = get_client_ip(request) + user_agent = parse(http_user_agent) + if not response.streaming: + try: + cast_time = time.time() - start_time + if ELASTICSEARCH_ENABLED: + time_taken = round((cast_time) * 1000, 2) + url = request.path + from django.utils import timezone + ElaspedTimeDocumentManager.create( + url=url, + time_taken=time_taken, + log_datetime=timezone.now(), + useragent=user_agent, + ip=ip) + response.content = response.content.replace( + b'', str.encode(str(cast_time)[:5])) + except Exception as e: + logger.error("Error OnlineMiddleware: %s" % e) + + return response diff --git a/src/blog/migrations/0001_initial.py b/src/DjangoBlog/blog/migrations/0001_initial.py similarity index 100% rename from src/blog/migrations/0001_initial.py rename to src/DjangoBlog/blog/migrations/0001_initial.py diff --git a/src/blog/migrations/0002_blogsettings_global_footer_and_more.py b/src/DjangoBlog/blog/migrations/0002_blogsettings_global_footer_and_more.py similarity index 100% rename from src/blog/migrations/0002_blogsettings_global_footer_and_more.py rename to src/DjangoBlog/blog/migrations/0002_blogsettings_global_footer_and_more.py diff --git a/src/blog/migrations/0003_blogsettings_comment_need_review.py b/src/DjangoBlog/blog/migrations/0003_blogsettings_comment_need_review.py similarity index 100% rename from src/blog/migrations/0003_blogsettings_comment_need_review.py rename to src/DjangoBlog/blog/migrations/0003_blogsettings_comment_need_review.py diff --git a/src/blog/migrations/0004_rename_analyticscode_blogsettings_analytics_code_and_more.py b/src/DjangoBlog/blog/migrations/0004_rename_analyticscode_blogsettings_analytics_code_and_more.py similarity index 100% rename from src/blog/migrations/0004_rename_analyticscode_blogsettings_analytics_code_and_more.py rename to src/DjangoBlog/blog/migrations/0004_rename_analyticscode_blogsettings_analytics_code_and_more.py diff --git a/src/blog/migrations/0005_alter_article_options_alter_category_options_and_more.py b/src/DjangoBlog/blog/migrations/0005_alter_article_options_alter_category_options_and_more.py similarity index 100% rename from src/blog/migrations/0005_alter_article_options_alter_category_options_and_more.py rename to src/DjangoBlog/blog/migrations/0005_alter_article_options_alter_category_options_and_more.py diff --git a/src/blog/migrations/0006_alter_blogsettings_options.py b/src/DjangoBlog/blog/migrations/0006_alter_blogsettings_options.py similarity index 100% rename from src/blog/migrations/0006_alter_blogsettings_options.py rename to src/DjangoBlog/blog/migrations/0006_alter_blogsettings_options.py diff --git a/src/blog/migrations/__init__.py b/src/DjangoBlog/blog/migrations/__init__.py similarity index 100% rename from src/blog/migrations/__init__.py rename to src/DjangoBlog/blog/migrations/__init__.py diff --git a/src/DjangoBlog/blog/migrations/__pycache__/0001_initial.cpython-310.pyc b/src/DjangoBlog/blog/migrations/__pycache__/0001_initial.cpython-310.pyc new file mode 100644 index 0000000..5d2e973 Binary files /dev/null and b/src/DjangoBlog/blog/migrations/__pycache__/0001_initial.cpython-310.pyc differ diff --git a/src/DjangoBlog/blog/migrations/__pycache__/0002_blogsettings_global_footer_and_more.cpython-310.pyc b/src/DjangoBlog/blog/migrations/__pycache__/0002_blogsettings_global_footer_and_more.cpython-310.pyc new file mode 100644 index 0000000..1caaf8b Binary files /dev/null and b/src/DjangoBlog/blog/migrations/__pycache__/0002_blogsettings_global_footer_and_more.cpython-310.pyc differ diff --git a/src/DjangoBlog/blog/migrations/__pycache__/0003_blogsettings_comment_need_review.cpython-310.pyc b/src/DjangoBlog/blog/migrations/__pycache__/0003_blogsettings_comment_need_review.cpython-310.pyc new file mode 100644 index 0000000..5590cd1 Binary files /dev/null and b/src/DjangoBlog/blog/migrations/__pycache__/0003_blogsettings_comment_need_review.cpython-310.pyc differ diff --git a/src/DjangoBlog/blog/migrations/__pycache__/0004_rename_analyticscode_blogsettings_analytics_code_and_more.cpython-310.pyc b/src/DjangoBlog/blog/migrations/__pycache__/0004_rename_analyticscode_blogsettings_analytics_code_and_more.cpython-310.pyc new file mode 100644 index 0000000..20e50fe Binary files /dev/null and b/src/DjangoBlog/blog/migrations/__pycache__/0004_rename_analyticscode_blogsettings_analytics_code_and_more.cpython-310.pyc differ diff --git a/src/DjangoBlog/blog/migrations/__pycache__/0005_alter_article_options_alter_category_options_and_more.cpython-310.pyc b/src/DjangoBlog/blog/migrations/__pycache__/0005_alter_article_options_alter_category_options_and_more.cpython-310.pyc new file mode 100644 index 0000000..031b234 Binary files /dev/null and b/src/DjangoBlog/blog/migrations/__pycache__/0005_alter_article_options_alter_category_options_and_more.cpython-310.pyc differ diff --git a/src/DjangoBlog/blog/migrations/__pycache__/0006_alter_blogsettings_options.cpython-310.pyc b/src/DjangoBlog/blog/migrations/__pycache__/0006_alter_blogsettings_options.cpython-310.pyc new file mode 100644 index 0000000..4b8e8bc Binary files /dev/null and b/src/DjangoBlog/blog/migrations/__pycache__/0006_alter_blogsettings_options.cpython-310.pyc differ diff --git a/src/DjangoBlog/blog/migrations/__pycache__/__init__.cpython-310.pyc b/src/DjangoBlog/blog/migrations/__pycache__/__init__.cpython-310.pyc new file mode 100644 index 0000000..aba6fe4 Binary files /dev/null and b/src/DjangoBlog/blog/migrations/__pycache__/__init__.cpython-310.pyc differ diff --git a/src/blog/models.py b/src/DjangoBlog/blog/models.py similarity index 100% rename from src/blog/models.py rename to src/DjangoBlog/blog/models.py diff --git a/src/blog/search_indexes.py b/src/DjangoBlog/blog/search_indexes.py similarity index 100% rename from src/blog/search_indexes.py rename to src/DjangoBlog/blog/search_indexes.py diff --git a/src/blog/static/account/css/account.css b/src/DjangoBlog/blog/static/account/css/account.css similarity index 100% rename from src/blog/static/account/css/account.css rename to src/DjangoBlog/blog/static/account/css/account.css diff --git a/src/blog/static/account/js/account.js b/src/DjangoBlog/blog/static/account/js/account.js similarity index 100% rename from src/blog/static/account/js/account.js rename to src/DjangoBlog/blog/static/account/js/account.js diff --git a/src/blog/static/assets/css/bootstrap.min.css b/src/DjangoBlog/blog/static/assets/css/bootstrap.min.css similarity index 100% rename from src/blog/static/assets/css/bootstrap.min.css rename to src/DjangoBlog/blog/static/assets/css/bootstrap.min.css diff --git a/src/blog/static/assets/css/docs.min.css b/src/DjangoBlog/blog/static/assets/css/docs.min.css similarity index 100% rename from src/blog/static/assets/css/docs.min.css rename to src/DjangoBlog/blog/static/assets/css/docs.min.css diff --git a/src/blog/static/assets/css/ie10-viewport-bug-workaround.css b/src/DjangoBlog/blog/static/assets/css/ie10-viewport-bug-workaround.css similarity index 100% rename from src/blog/static/assets/css/ie10-viewport-bug-workaround.css rename to src/DjangoBlog/blog/static/assets/css/ie10-viewport-bug-workaround.css diff --git a/src/blog/static/assets/css/signin.css b/src/DjangoBlog/blog/static/assets/css/signin.css similarity index 100% rename from src/blog/static/assets/css/signin.css rename to src/DjangoBlog/blog/static/assets/css/signin.css diff --git a/src/blog/static/assets/css/todc-bootstrap.min.css b/src/DjangoBlog/blog/static/assets/css/todc-bootstrap.min.css similarity index 100% rename from src/blog/static/assets/css/todc-bootstrap.min.css rename to src/DjangoBlog/blog/static/assets/css/todc-bootstrap.min.css diff --git a/src/blog/static/assets/img/checkmark.png b/src/DjangoBlog/blog/static/assets/img/checkmark.png similarity index 100% rename from src/blog/static/assets/img/checkmark.png rename to src/DjangoBlog/blog/static/assets/img/checkmark.png diff --git a/src/blog/static/assets/js/ie-emulation-modes-warning.js b/src/DjangoBlog/blog/static/assets/js/ie-emulation-modes-warning.js similarity index 100% rename from src/blog/static/assets/js/ie-emulation-modes-warning.js rename to src/DjangoBlog/blog/static/assets/js/ie-emulation-modes-warning.js diff --git a/src/blog/static/assets/js/ie10-viewport-bug-workaround.js b/src/DjangoBlog/blog/static/assets/js/ie10-viewport-bug-workaround.js similarity index 100% rename from src/blog/static/assets/js/ie10-viewport-bug-workaround.js rename to src/DjangoBlog/blog/static/assets/js/ie10-viewport-bug-workaround.js diff --git a/src/blog/static/blog/css/ie.css b/src/DjangoBlog/blog/static/blog/css/ie.css similarity index 100% rename from src/blog/static/blog/css/ie.css rename to src/DjangoBlog/blog/static/blog/css/ie.css diff --git a/src/blog/static/blog/css/nprogress.css b/src/DjangoBlog/blog/static/blog/css/nprogress.css similarity index 100% rename from src/blog/static/blog/css/nprogress.css rename to src/DjangoBlog/blog/static/blog/css/nprogress.css diff --git a/src/blog/static/blog/css/oauth_style.css b/src/DjangoBlog/blog/static/blog/css/oauth_style.css similarity index 100% rename from src/blog/static/blog/css/oauth_style.css rename to src/DjangoBlog/blog/static/blog/css/oauth_style.css diff --git a/src/blog/static/blog/css/style.css b/src/DjangoBlog/blog/static/blog/css/style.css similarity index 83% rename from src/blog/static/blog/css/style.css rename to src/DjangoBlog/blog/static/blog/css/style.css index cdbd790..d43f7f3 100644 --- a/src/blog/static/blog/css/style.css +++ b/src/DjangoBlog/blog/static/blog/css/style.css @@ -2017,7 +2017,12 @@ img#wpstats { width: auto; } - + .commentlist .avatar { + height: 39px; + left: 2.2em; + top: 2.2em; + width: 39px; + } .comments-area article header cite, .comments-area article header time { @@ -2145,70 +2150,17 @@ div { word-break: break-all; } -/* 评论整体布局 - 使用相对定位实现头像左侧布局 */ -.commentlist .comment-body { - position: relative; - padding-left: 60px; /* 为48px头像 + 12px间距留出空间 */ - min-height: 48px; /* 确保有足够高度容纳头像 */ -} - -/* 评论作者信息 - 用户名和时间在同一行 */ -.commentlist .comment-author { - display: inline-block; - margin: 0 10px 5px 0; - font-size: 13px; - position: relative; -} - -.commentlist .comment-meta { - display: inline-block; - margin: 0 0 8px 0; - font-size: 12px; - color: #666; -} - +.commentlist .comment-author, +.commentlist .comment-meta, .commentlist .comment-awaiting-moderation { + float: left; display: block; font-size: 13px; line-height: 22px; } -/* 头像样式 - 绝对定位到左侧 */ -.commentlist .comment-author .avatar { - position: absolute !important; - left: -60px; /* 定位到容器左侧 */ - top: 0; - width: 48px !important; - height: 48px !important; - border-radius: 50%; - display: block; - object-fit: cover; - background-color: #f5f5f5; - border: 1px solid #ddd; -} - -/* 评论作者名称样式 */ -.commentlist .comment-author .fn { - display: inline; - margin: 0; - font-weight: 600; - color: #2e7bb8; - font-size: 13px; -} - -.commentlist .comment-author .fn a { - color: #2e7bb8; - text-decoration: none; -} - -.commentlist .comment-author .fn a:hover { - text-decoration: underline; -} - -/* 评论内容样式 */ -.commentlist .comment-body p { - margin: 5px 0 10px 0; - line-height: 1.5; +.commentlist .comment-author { + margin-right: 6px; } .commentlist .fn, .pinglist .ping-link { @@ -2222,15 +2174,13 @@ div { display: none; } -/* 通用头像样式 */ .commentlist .avatar { - width: 48px !important; - height: 48px !important; - border-radius: 50%; - display: block; - object-fit: cover; - background-color: #f5f5f5; - border: 1px solid #ddd; + position: absolute; + left: -60px; + top: 0; + width: 48px; + height: 48px; + border-radius: 100%; } .commentlist .comment-meta:before, .pinglist .ping-meta:before { @@ -2340,87 +2290,15 @@ div { padding-left: 48px; } -/* 嵌套评论整体布局 */ -.commentlist li li .comment-body { - padding-left: 60px; /* 为48px头像 + 12px间距留出空间 */ - min-height: 48px; /* 确保有足够高度容纳头像 */ -} - -/* 嵌套评论作者信息 */ -.commentlist li li .comment-author { - display: inline-block; - margin: 0 8px 5px 0; - font-size: 12px; /* 稍小一点 */ +.commentlist li li .avatar { + top: 0; + left: -48px; + width: 36px; + height: 36px; } .commentlist li li .comment-meta { - display: inline-block; - margin: 0 0 8px 0; - font-size: 11px; /* 稍小一点 */ - color: #666; -} - -/* 评论容器整体左移 - 使用更高优先级 */ -#comments #commentlist-container.comment-tab { - margin-left: -15px !important; /* 在小屏幕上向左移动15px */ - padding-left: 0 !important; /* 移除左内边距 */ - position: relative !important; /* 确保定位正确 */ -} - -/* 在较大屏幕上进一步左移 */ -@media screen and (min-width: 600px) { - #comments #commentlist-container.comment-tab { - margin-left: -30px !important; /* 在大屏幕上向左移动30px */ - } - - /* 响应式设计下的评论布局 - 保持48px头像 */ - .commentlist .comment-body { - padding-left: 60px !important; /* 为48px头像 + 12px间距留出空间 */ - min-height: 48px !important; - } - - .commentlist .comment-author { - display: inline-block !important; - margin: 0 8px 5px 0 !important; - } - - .commentlist .comment-meta { - display: inline-block !important; - margin: 0 0 8px 0 !important; - } - - /* 响应式设计下头像保持48px */ - .commentlist .comment-author .avatar { - left: -60px !important; - width: 48px !important; - height: 48px !important; - } - - /* 嵌套评论在响应式设计下也保持48px头像 */ - .commentlist li li .comment-body { - padding-left: 60px !important; - min-height: 48px !important; - } - - .commentlist li li .comment-author .avatar { - left: -60px !important; - width: 48px !important; - height: 48px !important; - } -} - -/* 嵌套评论头像 */ -.commentlist li li .comment-author .avatar { - position: absolute !important; - left: -60px; /* 定位到容器左侧 */ - top: 0; - width: 48px !important; - height: 48px !important; - border-radius: 50%; - display: block; - object-fit: cover; - background-color: #f5f5f5; - border: 1px solid #ddd; + left: 70px; } /* comments : nav @@ -2623,276 +2501,4 @@ li #reply-title { height: 1px; border: none; /*border-top: 1px dashed #f5d6d6;*/ -} - -/* ============================================================================= - 评论内容溢出修复样式 - 解决代码块和长文本撑开页面布局的问题 - ============================================================================= */ - -/* 评论容器基础样式 */ -.comment-body { - overflow-wrap: break-word; - word-wrap: break-word; - word-break: break-word; - max-width: 100%; - box-sizing: border-box; -} - -/* 修复评论中的代码块溢出 */ -.comment-content pre, -.comment-body pre { - white-space: pre-wrap !important; - word-wrap: break-word !important; - overflow-wrap: break-word !important; - max-width: 100% !important; - overflow-x: auto; - padding: 10px; - background-color: #f8f8f8; - border: 1px solid #ddd; - border-radius: 4px; - font-size: 12px; - line-height: 1.4; - margin: 10px 0; -} - -/* 修复评论中的行内代码 */ -.comment-content code, -.comment-body code { - word-wrap: break-word !important; - overflow-wrap: break-word !important; - white-space: pre-wrap; - max-width: 100%; - display: inline-block; - vertical-align: top; -} - -/* 修复评论中的长链接 */ -.comment-content a, -.comment-body a { - word-wrap: break-word !important; - overflow-wrap: break-word !important; - word-break: break-all; - max-width: 100%; -} - -/* 修复评论段落 */ -.comment-content p, -.comment-body p { - word-wrap: break-word !important; - overflow-wrap: break-word !important; - max-width: 100%; - margin: 10px 0; -} - -/* 特殊处理代码高亮块 - 关键修复! */ -.comment-content .codehilite, -.comment-body .codehilite { - max-width: 100% !important; - overflow-x: auto; - margin: 10px 0; - background: #f8f8f8 !important; - border: 1px solid #ddd; - border-radius: 4px; - padding: 10px; - font-size: 12px; - line-height: 1.4; - /* 关键:防止内容撑开容器 */ - width: 100%; - box-sizing: border-box; - display: block; -} - -.comment-content .codehilite pre, -.comment-body .codehilite pre { - white-space: pre-wrap !important; - word-wrap: break-word !important; - overflow-wrap: break-word !important; - margin: 0 !important; - padding: 0 !important; - background: transparent !important; - border: none !important; - font-size: inherit; - line-height: inherit; - /* 确保pre标签不会超出父容器 */ - max-width: 100%; - width: 100%; - box-sizing: border-box; -} - -/* 修复代码高亮中的span标签 */ -.comment-content .codehilite span, -.comment-body .codehilite span { - word-wrap: break-word !important; - overflow-wrap: break-word !important; - /* 防止行内元素导致的溢出 */ - display: inline; - max-width: 100%; -} - -/* 针对特定的代码高亮类 */ -.comment-content .codehilite .kt, -.comment-content .codehilite .nf, -.comment-content .codehilite .n, -.comment-content .codehilite .p, -.comment-body .codehilite .kt, -.comment-body .codehilite .nf, -.comment-body .codehilite .n, -.comment-body .codehilite .p { - word-wrap: break-word !important; - overflow-wrap: break-word !important; -} - -/* 搜索结果高亮样式 */ -.search-result { - margin-bottom: 30px; - padding: 20px; - border: 1px solid #e1e1e1; - border-radius: 5px; - background: #fff; -} - -.search-result .entry-title { - margin: 0 0 10px 0; - font-size: 1.5em; -} - -.search-result .entry-title a { - color: #2c3e50; - text-decoration: none; -} - -.search-result .entry-title a:hover { - color: #3498db; -} - -.search-result .entry-meta { - color: #7f8c8d; - font-size: 0.9em; - margin-bottom: 15px; -} - -.search-result .entry-meta span { - margin-right: 15px; -} - -.search-excerpt { - line-height: 1.6; - color: #555; -} - -.search-excerpt p { - margin: 10px 0; -} - -/* 搜索关键词高亮 */ -.search-excerpt em, -.search-result .entry-title em { - background-color: #fff3cd; - color: #856404; - font-style: normal; - font-weight: bold; - padding: 2px 4px; - border-radius: 3px; -} - -.more-link { - color: #3498db; - text-decoration: none; - font-weight: bold; -} - -.more-link:hover { - text-decoration: underline; -} -.comment-content .codehilite .w, -.comment-content .codehilite .o, -.comment-body .codehilite .kt, -.comment-body .codehilite .nf, -.comment-body .codehilite .n, -.comment-body .codehilite .p, -.comment-body .codehilite .w, -.comment-body .codehilite .o { - word-break: break-all; - overflow-wrap: break-word; -} - -/* 修复评论列表项 */ -.commentlist li { - max-width: 100%; - overflow: hidden; - box-sizing: border-box; -} - -/* 确保评论内容不超出容器 */ -.commentlist .comment-body { - max-width: calc(100% - 20px); /* 留出一些边距 */ - margin-left: 10px; - margin-right: 10px; - overflow: hidden; /* 防止内容溢出 */ - word-wrap: break-word; -} - -/* 重要:限制评论列表项的最大宽度 */ -.commentlist li[style*="margin-left"] { - max-width: calc(100% - 2rem) !important; - overflow: hidden; - box-sizing: border-box; -} - -/* 特别处理深层嵌套的评论 */ -.commentlist li[style*="margin-left: 3rem"], -.commentlist li[style*="margin-left: 6rem"], -.commentlist li[style*="margin-left: 9rem"] { - max-width: calc(100% - 1rem) !important; -} - -/* 移动端优化 */ -@media (max-width: 768px) { - .comment-content pre, - .comment-body pre { - font-size: 11px; - padding: 8px; - margin: 8px 0; - } - - .commentlist .comment-body { - max-width: calc(100% - 10px); - margin-left: 5px; - margin-right: 5px; - } - - /* 移动端评论缩进调整 */ - .commentlist li[style*="margin-left"] { - margin-left: 1rem !important; - max-margin-left: 2rem !important; - } -} - -/* 防止表格溢出 */ -.comment-content table, -.comment-body table { - max-width: 100%; - overflow-x: auto; - display: block; - white-space: nowrap; -} - -/* 修复图片溢出 */ -.comment-content img, -.comment-body img { - max-width: 100% !important; - height: auto !important; -} - -/* 修复引用块 */ -.comment-content blockquote, -.comment-body blockquote { - max-width: 100%; - overflow-wrap: break-word; - word-wrap: break-word; - padding: 10px 15px; - margin: 10px 0; - border-left: 4px solid #ddd; - background-color: #f9f9f9; } \ No newline at end of file diff --git a/src/DjangoBlog/blog/static/blog/fonts/fonts.css b/src/DjangoBlog/blog/static/blog/fonts/fonts.css new file mode 100644 index 0000000..c1a29cf --- /dev/null +++ b/src/DjangoBlog/blog/static/blog/fonts/fonts.css @@ -0,0 +1,378 @@ +/* cyrillic-ext */ +@font-face { + font-family: 'Open Sans'; + font-style: italic; + font-weight: 300; + font-display: fallback; + src: url(memnYaGs126MiZpBA-UFUKWyV9hmIqOjjg.woff2) format('woff2'); + unicode-range: U+0460-052F, U+1C80-1C88, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F; +} +/* cyrillic */ +@font-face { + font-family: 'Open Sans'; + font-style: italic; + font-weight: 300; + font-display: fallback; + src: url(memnYaGs126MiZpBA-UFUKWyV9hvIqOjjg.woff2) format('woff2'); + unicode-range: U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116; +} +/* greek-ext */ +@font-face { + font-family: 'Open Sans'; + font-style: italic; + font-weight: 300; + font-display: fallback; + src: url(memnYaGs126MiZpBA-UFUKWyV9hnIqOjjg.woff2) format('woff2'); + unicode-range: U+1F00-1FFF; +} +/* greek */ +@font-face { + font-family: 'Open Sans'; + font-style: italic; + font-weight: 300; + font-display: fallback; + src: url(memnYaGs126MiZpBA-UFUKWyV9hoIqOjjg.woff2) format('woff2'); + unicode-range: U+0370-03FF; +} +/* vietnamese */ +@font-face { + font-family: 'Open Sans'; + font-style: italic; + font-weight: 300; + font-display: fallback; + src: url(memnYaGs126MiZpBA-UFUKWyV9hkIqOjjg.woff2) format('woff2'); + unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+1EA0-1EF9, U+20AB; +} +/* latin-ext */ +@font-face { + font-family: 'Open Sans'; + font-style: italic; + font-weight: 300; + font-display: fallback; + src: url(memnYaGs126MiZpBA-UFUKWyV9hlIqOjjg.woff2) format('woff2'); + unicode-range: U+0100-024F, U+0259, U+1E00-1EFF, U+2020, U+20A0-20AB, U+20AD-20CF, U+2113, U+2C60-2C7F, U+A720-A7FF; +} +/* latin */ +@font-face { + font-family: 'Open Sans'; + font-style: italic; + font-weight: 300; + font-display: fallback; + src: url(memnYaGs126MiZpBA-UFUKWyV9hrIqM.woff2) format('woff2'); + unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD; +} +/* cyrillic-ext */ +@font-face { + font-family: 'Open Sans'; + font-style: italic; + font-weight: 400; + font-display: fallback; + src: url(mem6YaGs126MiZpBA-UFUK0Udc1UAw.woff2) format('woff2'); + unicode-range: U+0460-052F, U+1C80-1C88, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F; +} +/* cyrillic */ +@font-face { + font-family: 'Open Sans'; + font-style: italic; + font-weight: 400; + font-display: fallback; + src: url(mem6YaGs126MiZpBA-UFUK0ddc1UAw.woff2) format('woff2'); + unicode-range: U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116; +} +/* greek-ext */ +@font-face { + font-family: 'Open Sans'; + font-style: italic; + font-weight: 400; + font-display: fallback; + src: url(mem6YaGs126MiZpBA-UFUK0Vdc1UAw.woff2) format('woff2'); + unicode-range: U+1F00-1FFF; +} +/* greek */ +@font-face { + font-family: 'Open Sans'; + font-style: italic; + font-weight: 400; + font-display: fallback; + src: url(mem6YaGs126MiZpBA-UFUK0adc1UAw.woff2) format('woff2'); + unicode-range: U+0370-03FF; +} +/* vietnamese */ +@font-face { + font-family: 'Open Sans'; + font-style: italic; + font-weight: 400; + font-display: fallback; + src: url(mem6YaGs126MiZpBA-UFUK0Wdc1UAw.woff2) format('woff2'); + unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+1EA0-1EF9, U+20AB; +} +/* latin-ext */ +@font-face { + font-family: 'Open Sans'; + font-style: italic; + font-weight: 400; + font-display: fallback; + src: url(mem6YaGs126MiZpBA-UFUK0Xdc1UAw.woff2) format('woff2'); + unicode-range: U+0100-024F, U+0259, U+1E00-1EFF, U+2020, U+20A0-20AB, U+20AD-20CF, U+2113, U+2C60-2C7F, U+A720-A7FF; +} +/* latin */ +@font-face { + font-family: 'Open Sans'; + font-style: italic; + font-weight: 400; + font-display: fallback; + src: url(mem6YaGs126MiZpBA-UFUK0Zdc0.woff2) format('woff2'); + unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD; +} +/* cyrillic-ext */ +@font-face { + font-family: 'Open Sans'; + font-style: italic; + font-weight: 600; + font-display: fallback; + src: url(memnYaGs126MiZpBA-UFUKXGUdhmIqOjjg.woff2) format('woff2'); + unicode-range: U+0460-052F, U+1C80-1C88, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F; +} +/* cyrillic */ +@font-face { + font-family: 'Open Sans'; + font-style: italic; + font-weight: 600; + font-display: fallback; + src: url(memnYaGs126MiZpBA-UFUKXGUdhvIqOjjg.woff2) format('woff2'); + unicode-range: U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116; +} +/* greek-ext */ +@font-face { + font-family: 'Open Sans'; + font-style: italic; + font-weight: 600; + font-display: fallback; + src: url(memnYaGs126MiZpBA-UFUKXGUdhnIqOjjg.woff2) format('woff2'); + unicode-range: U+1F00-1FFF; +} +/* greek */ +@font-face { + font-family: 'Open Sans'; + font-style: italic; + font-weight: 600; + font-display: fallback; + src: url(memnYaGs126MiZpBA-UFUKXGUdhoIqOjjg.woff2) format('woff2'); + unicode-range: U+0370-03FF; +} +/* vietnamese */ +@font-face { + font-family: 'Open Sans'; + font-style: italic; + font-weight: 600; + font-display: fallback; + src: url(memnYaGs126MiZpBA-UFUKXGUdhkIqOjjg.woff2) format('woff2'); + unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+1EA0-1EF9, U+20AB; +} +/* latin-ext */ +@font-face { + font-family: 'Open Sans'; + font-style: italic; + font-weight: 600; + font-display: fallback; + src: url(memnYaGs126MiZpBA-UFUKXGUdhlIqOjjg.woff2) format('woff2'); + unicode-range: U+0100-024F, U+0259, U+1E00-1EFF, U+2020, U+20A0-20AB, U+20AD-20CF, U+2113, U+2C60-2C7F, U+A720-A7FF; +} +/* latin */ +@font-face { + font-family: 'Open Sans'; + font-style: italic; + font-weight: 600; + font-display: fallback; + src: url(memnYaGs126MiZpBA-UFUKXGUdhrIqM.woff2) format('woff2'); + unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD; +} +/* cyrillic-ext */ +@font-face { + font-family: 'Open Sans'; + font-style: normal; + font-weight: 300; + font-display: fallback; + src: url(mem5YaGs126MiZpBA-UN_r8OX-hpOqc.woff2) format('woff2'); + unicode-range: U+0460-052F, U+1C80-1C88, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F; +} +/* cyrillic */ +@font-face { + font-family: 'Open Sans'; + font-style: normal; + font-weight: 300; + font-display: fallback; + src: url(mem5YaGs126MiZpBA-UN_r8OVuhpOqc.woff2) format('woff2'); + unicode-range: U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116; +} +/* greek-ext */ +@font-face { + font-family: 'Open Sans'; + font-style: normal; + font-weight: 300; + font-display: fallback; + src: url(mem5YaGs126MiZpBA-UN_r8OXuhpOqc.woff2) format('woff2'); + unicode-range: U+1F00-1FFF; +} +/* greek */ +@font-face { + font-family: 'Open Sans'; + font-style: normal; + font-weight: 300; + font-display: fallback; + src: url(mem5YaGs126MiZpBA-UN_r8OUehpOqc.woff2) format('woff2'); + unicode-range: U+0370-03FF; +} +/* vietnamese */ +@font-face { + font-family: 'Open Sans'; + font-style: normal; + font-weight: 300; + font-display: fallback; + src: url(mem5YaGs126MiZpBA-UN_r8OXehpOqc.woff2) format('woff2'); + unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+1EA0-1EF9, U+20AB; +} +/* latin-ext */ +@font-face { + font-family: 'Open Sans'; + font-style: normal; + font-weight: 300; + font-display: fallback; + src: url(mem5YaGs126MiZpBA-UN_r8OXOhpOqc.woff2) format('woff2'); + unicode-range: U+0100-024F, U+0259, U+1E00-1EFF, U+2020, U+20A0-20AB, U+20AD-20CF, U+2113, U+2C60-2C7F, U+A720-A7FF; +} +/* latin */ +@font-face { + font-family: 'Open Sans'; + font-style: normal; + font-weight: 300; + font-display: fallback; + src: url(mem5YaGs126MiZpBA-UN_r8OUuhp.woff2) format('woff2'); + unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD; +} +/* cyrillic-ext */ +@font-face { + font-family: 'Open Sans'; + font-style: normal; + font-weight: 400; + font-display: fallback; + src: url(mem8YaGs126MiZpBA-UFWJ0bbck.woff2) format('woff2'); + unicode-range: U+0460-052F, U+1C80-1C88, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F; +} +/* cyrillic */ +@font-face { + font-family: 'Open Sans'; + font-style: normal; + font-weight: 400; + font-display: fallback; + src: url(mem8YaGs126MiZpBA-UFUZ0bbck.woff2) format('woff2'); + unicode-range: U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116; +} +/* greek-ext */ +@font-face { + font-family: 'Open Sans'; + font-style: normal; + font-weight: 400; + font-display: fallback; + src: url(mem8YaGs126MiZpBA-UFWZ0bbck.woff2) format('woff2'); + unicode-range: U+1F00-1FFF; +} +/* greek */ +@font-face { + font-family: 'Open Sans'; + font-style: normal; + font-weight: 400; + font-display: fallback; + src: url(mem8YaGs126MiZpBA-UFVp0bbck.woff2) format('woff2'); + unicode-range: U+0370-03FF; +} +/* vietnamese */ +@font-face { + font-family: 'Open Sans'; + font-style: normal; + font-weight: 400; + font-display: fallback; + src: url(mem8YaGs126MiZpBA-UFWp0bbck.woff2) format('woff2'); + unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+1EA0-1EF9, U+20AB; +} +/* latin-ext */ +@font-face { + font-family: 'Open Sans'; + font-style: normal; + font-weight: 400; + font-display: fallback; + src: url(mem8YaGs126MiZpBA-UFW50bbck.woff2) format('woff2'); + unicode-range: U+0100-024F, U+0259, U+1E00-1EFF, U+2020, U+20A0-20AB, U+20AD-20CF, U+2113, U+2C60-2C7F, U+A720-A7FF; +} +/* latin */ +@font-face { + font-family: 'Open Sans'; + font-style: normal; + font-weight: 400; + font-display: fallback; + src: url(mem8YaGs126MiZpBA-UFVZ0b.woff2) format('woff2'); + unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD; +} +/* cyrillic-ext */ +@font-face { + font-family: 'Open Sans'; + font-style: normal; + font-weight: 600; + font-display: fallback; + src: url(mem5YaGs126MiZpBA-UNirkOX-hpOqc.woff2) format('woff2'); + unicode-range: U+0460-052F, U+1C80-1C88, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F; +} +/* cyrillic */ +@font-face { + font-family: 'Open Sans'; + font-style: normal; + font-weight: 600; + font-display: fallback; + src: url(mem5YaGs126MiZpBA-UNirkOVuhpOqc.woff2) format('woff2'); + unicode-range: U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116; +} +/* greek-ext */ +@font-face { + font-family: 'Open Sans'; + font-style: normal; + font-weight: 600; + font-display: fallback; + src: url(mem5YaGs126MiZpBA-UNirkOXuhpOqc.woff2) format('woff2'); + unicode-range: U+1F00-1FFF; +} +/* greek */ +@font-face { + font-family: 'Open Sans'; + font-style: normal; + font-weight: 600; + font-display: fallback; + src: url(mem5YaGs126MiZpBA-UNirkOUehpOqc.woff2) format('woff2'); + unicode-range: U+0370-03FF; +} +/* vietnamese */ +@font-face { + font-family: 'Open Sans'; + font-style: normal; + font-weight: 600; + font-display: fallback; + src: url(mem5YaGs126MiZpBA-UNirkOXehpOqc.woff2) format('woff2'); + unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+1EA0-1EF9, U+20AB; +} +/* latin-ext */ +@font-face { + font-family: 'Open Sans'; + font-style: normal; + font-weight: 600; + font-display: fallback; + src: url(mem5YaGs126MiZpBA-UNirkOXOhpOqc.woff2) format('woff2'); + unicode-range: U+0100-024F, U+0259, U+1E00-1EFF, U+2020, U+20A0-20AB, U+20AD-20CF, U+2113, U+2C60-2C7F, U+A720-A7FF; +} +/* latin */ +@font-face { + font-family: 'Open Sans'; + font-style: normal; + font-weight: 600; + font-display: fallback; + src: url(mem5YaGs126MiZpBA-UNirkOUuhp.woff2) format('woff2'); + unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD; +} diff --git a/src/DjangoBlog/blog/static/blog/fonts/mem5YaGs126MiZpBA-UN_r8OUehpOqc.woff2 b/src/DjangoBlog/blog/static/blog/fonts/mem5YaGs126MiZpBA-UN_r8OUehpOqc.woff2 new file mode 100644 index 0000000..2c47cc5 Binary files /dev/null and b/src/DjangoBlog/blog/static/blog/fonts/mem5YaGs126MiZpBA-UN_r8OUehpOqc.woff2 differ diff --git a/src/DjangoBlog/blog/static/blog/fonts/mem5YaGs126MiZpBA-UN_r8OUuhp.woff2 b/src/DjangoBlog/blog/static/blog/fonts/mem5YaGs126MiZpBA-UN_r8OUuhp.woff2 new file mode 100644 index 0000000..601706a Binary files /dev/null and b/src/DjangoBlog/blog/static/blog/fonts/mem5YaGs126MiZpBA-UN_r8OUuhp.woff2 differ diff --git a/src/DjangoBlog/blog/static/blog/fonts/mem5YaGs126MiZpBA-UN_r8OVuhpOqc.woff2 b/src/DjangoBlog/blog/static/blog/fonts/mem5YaGs126MiZpBA-UN_r8OVuhpOqc.woff2 new file mode 100644 index 0000000..119f1d7 Binary files /dev/null and b/src/DjangoBlog/blog/static/blog/fonts/mem5YaGs126MiZpBA-UN_r8OVuhpOqc.woff2 differ diff --git a/src/DjangoBlog/blog/static/blog/fonts/mem5YaGs126MiZpBA-UN_r8OX-hpOqc.woff2 b/src/DjangoBlog/blog/static/blog/fonts/mem5YaGs126MiZpBA-UN_r8OX-hpOqc.woff2 new file mode 100644 index 0000000..d56688f Binary files /dev/null and b/src/DjangoBlog/blog/static/blog/fonts/mem5YaGs126MiZpBA-UN_r8OX-hpOqc.woff2 differ diff --git a/src/DjangoBlog/blog/static/blog/fonts/mem5YaGs126MiZpBA-UN_r8OXOhpOqc.woff2 b/src/DjangoBlog/blog/static/blog/fonts/mem5YaGs126MiZpBA-UN_r8OXOhpOqc.woff2 new file mode 100644 index 0000000..e1f546c Binary files /dev/null and b/src/DjangoBlog/blog/static/blog/fonts/mem5YaGs126MiZpBA-UN_r8OXOhpOqc.woff2 differ diff --git a/src/DjangoBlog/blog/static/blog/fonts/mem5YaGs126MiZpBA-UN_r8OXehpOqc.woff2 b/src/DjangoBlog/blog/static/blog/fonts/mem5YaGs126MiZpBA-UN_r8OXehpOqc.woff2 new file mode 100644 index 0000000..0f17e3d Binary files /dev/null and b/src/DjangoBlog/blog/static/blog/fonts/mem5YaGs126MiZpBA-UN_r8OXehpOqc.woff2 differ diff --git a/src/DjangoBlog/blog/static/blog/fonts/mem5YaGs126MiZpBA-UN_r8OXuhpOqc.woff2 b/src/DjangoBlog/blog/static/blog/fonts/mem5YaGs126MiZpBA-UN_r8OXuhpOqc.woff2 new file mode 100644 index 0000000..50d8183 Binary files /dev/null and b/src/DjangoBlog/blog/static/blog/fonts/mem5YaGs126MiZpBA-UN_r8OXuhpOqc.woff2 differ diff --git a/src/DjangoBlog/blog/static/blog/fonts/mem5YaGs126MiZpBA-UNirkOUehpOqc.woff2 b/src/DjangoBlog/blog/static/blog/fonts/mem5YaGs126MiZpBA-UNirkOUehpOqc.woff2 new file mode 100644 index 0000000..b935198 Binary files /dev/null and b/src/DjangoBlog/blog/static/blog/fonts/mem5YaGs126MiZpBA-UNirkOUehpOqc.woff2 differ diff --git a/src/DjangoBlog/blog/static/blog/fonts/mem5YaGs126MiZpBA-UNirkOUuhp.woff2 b/src/DjangoBlog/blog/static/blog/fonts/mem5YaGs126MiZpBA-UNirkOUuhp.woff2 new file mode 100644 index 0000000..d77bb4c Binary files /dev/null and b/src/DjangoBlog/blog/static/blog/fonts/mem5YaGs126MiZpBA-UNirkOUuhp.woff2 differ diff --git a/src/DjangoBlog/blog/static/blog/fonts/mem5YaGs126MiZpBA-UNirkOVuhpOqc.woff2 b/src/DjangoBlog/blog/static/blog/fonts/mem5YaGs126MiZpBA-UNirkOVuhpOqc.woff2 new file mode 100644 index 0000000..e293ffc Binary files /dev/null and b/src/DjangoBlog/blog/static/blog/fonts/mem5YaGs126MiZpBA-UNirkOVuhpOqc.woff2 differ diff --git a/src/DjangoBlog/blog/static/blog/fonts/mem5YaGs126MiZpBA-UNirkOX-hpOqc.woff2 b/src/DjangoBlog/blog/static/blog/fonts/mem5YaGs126MiZpBA-UNirkOX-hpOqc.woff2 new file mode 100644 index 0000000..46fd61b Binary files /dev/null and b/src/DjangoBlog/blog/static/blog/fonts/mem5YaGs126MiZpBA-UNirkOX-hpOqc.woff2 differ diff --git a/src/DjangoBlog/blog/static/blog/fonts/mem5YaGs126MiZpBA-UNirkOXOhpOqc.woff2 b/src/DjangoBlog/blog/static/blog/fonts/mem5YaGs126MiZpBA-UNirkOXOhpOqc.woff2 new file mode 100644 index 0000000..88a1616 Binary files /dev/null and b/src/DjangoBlog/blog/static/blog/fonts/mem5YaGs126MiZpBA-UNirkOXOhpOqc.woff2 differ diff --git a/src/DjangoBlog/blog/static/blog/fonts/mem5YaGs126MiZpBA-UNirkOXehpOqc.woff2 b/src/DjangoBlog/blog/static/blog/fonts/mem5YaGs126MiZpBA-UNirkOXehpOqc.woff2 new file mode 100644 index 0000000..2100b6b Binary files /dev/null and b/src/DjangoBlog/blog/static/blog/fonts/mem5YaGs126MiZpBA-UNirkOXehpOqc.woff2 differ diff --git a/src/DjangoBlog/blog/static/blog/fonts/mem5YaGs126MiZpBA-UNirkOXuhpOqc.woff2 b/src/DjangoBlog/blog/static/blog/fonts/mem5YaGs126MiZpBA-UNirkOXuhpOqc.woff2 new file mode 100644 index 0000000..d54c7c0 Binary files /dev/null and b/src/DjangoBlog/blog/static/blog/fonts/mem5YaGs126MiZpBA-UNirkOXuhpOqc.woff2 differ diff --git a/src/DjangoBlog/blog/static/blog/fonts/mem6YaGs126MiZpBA-UFUK0Udc1UAw.woff2 b/src/DjangoBlog/blog/static/blog/fonts/mem6YaGs126MiZpBA-UFUK0Udc1UAw.woff2 new file mode 100644 index 0000000..683014d Binary files /dev/null and b/src/DjangoBlog/blog/static/blog/fonts/mem6YaGs126MiZpBA-UFUK0Udc1UAw.woff2 differ diff --git a/src/DjangoBlog/blog/static/blog/fonts/mem6YaGs126MiZpBA-UFUK0Vdc1UAw.woff2 b/src/DjangoBlog/blog/static/blog/fonts/mem6YaGs126MiZpBA-UFUK0Vdc1UAw.woff2 new file mode 100644 index 0000000..72eb246 Binary files /dev/null and b/src/DjangoBlog/blog/static/blog/fonts/mem6YaGs126MiZpBA-UFUK0Vdc1UAw.woff2 differ diff --git a/src/DjangoBlog/blog/static/blog/fonts/mem6YaGs126MiZpBA-UFUK0Wdc1UAw.woff2 b/src/DjangoBlog/blog/static/blog/fonts/mem6YaGs126MiZpBA-UFUK0Wdc1UAw.woff2 new file mode 100644 index 0000000..6da5562 Binary files /dev/null and b/src/DjangoBlog/blog/static/blog/fonts/mem6YaGs126MiZpBA-UFUK0Wdc1UAw.woff2 differ diff --git a/src/DjangoBlog/blog/static/blog/fonts/mem6YaGs126MiZpBA-UFUK0Xdc1UAw.woff2 b/src/DjangoBlog/blog/static/blog/fonts/mem6YaGs126MiZpBA-UFUK0Xdc1UAw.woff2 new file mode 100644 index 0000000..2f22c67 Binary files /dev/null and b/src/DjangoBlog/blog/static/blog/fonts/mem6YaGs126MiZpBA-UFUK0Xdc1UAw.woff2 differ diff --git a/src/DjangoBlog/blog/static/blog/fonts/mem6YaGs126MiZpBA-UFUK0Zdc0.woff2 b/src/DjangoBlog/blog/static/blog/fonts/mem6YaGs126MiZpBA-UFUK0Zdc0.woff2 new file mode 100644 index 0000000..28c6c76 Binary files /dev/null and b/src/DjangoBlog/blog/static/blog/fonts/mem6YaGs126MiZpBA-UFUK0Zdc0.woff2 differ diff --git a/src/DjangoBlog/blog/static/blog/fonts/mem6YaGs126MiZpBA-UFUK0adc1UAw.woff2 b/src/DjangoBlog/blog/static/blog/fonts/mem6YaGs126MiZpBA-UFUK0adc1UAw.woff2 new file mode 100644 index 0000000..fdeb9a4 Binary files /dev/null and b/src/DjangoBlog/blog/static/blog/fonts/mem6YaGs126MiZpBA-UFUK0adc1UAw.woff2 differ diff --git a/src/DjangoBlog/blog/static/blog/fonts/mem6YaGs126MiZpBA-UFUK0ddc1UAw.woff2 b/src/DjangoBlog/blog/static/blog/fonts/mem6YaGs126MiZpBA-UFUK0ddc1UAw.woff2 new file mode 100644 index 0000000..2a48105 Binary files /dev/null and b/src/DjangoBlog/blog/static/blog/fonts/mem6YaGs126MiZpBA-UFUK0ddc1UAw.woff2 differ diff --git a/src/DjangoBlog/blog/static/blog/fonts/mem8YaGs126MiZpBA-UFUZ0bbck.woff2 b/src/DjangoBlog/blog/static/blog/fonts/mem8YaGs126MiZpBA-UFUZ0bbck.woff2 new file mode 100644 index 0000000..1ddef14 Binary files /dev/null and b/src/DjangoBlog/blog/static/blog/fonts/mem8YaGs126MiZpBA-UFUZ0bbck.woff2 differ diff --git a/src/DjangoBlog/blog/static/blog/fonts/mem8YaGs126MiZpBA-UFVZ0b.woff2 b/src/DjangoBlog/blog/static/blog/fonts/mem8YaGs126MiZpBA-UFVZ0b.woff2 new file mode 100644 index 0000000..1d5e847 Binary files /dev/null and b/src/DjangoBlog/blog/static/blog/fonts/mem8YaGs126MiZpBA-UFVZ0b.woff2 differ diff --git a/src/DjangoBlog/blog/static/blog/fonts/mem8YaGs126MiZpBA-UFVp0bbck.woff2 b/src/DjangoBlog/blog/static/blog/fonts/mem8YaGs126MiZpBA-UFVp0bbck.woff2 new file mode 100644 index 0000000..0e22822 Binary files /dev/null and b/src/DjangoBlog/blog/static/blog/fonts/mem8YaGs126MiZpBA-UFVp0bbck.woff2 differ diff --git a/src/DjangoBlog/blog/static/blog/fonts/mem8YaGs126MiZpBA-UFW50bbck.woff2 b/src/DjangoBlog/blog/static/blog/fonts/mem8YaGs126MiZpBA-UFW50bbck.woff2 new file mode 100644 index 0000000..f621005 Binary files /dev/null and b/src/DjangoBlog/blog/static/blog/fonts/mem8YaGs126MiZpBA-UFW50bbck.woff2 differ diff --git a/src/DjangoBlog/blog/static/blog/fonts/mem8YaGs126MiZpBA-UFWJ0bbck.woff2 b/src/DjangoBlog/blog/static/blog/fonts/mem8YaGs126MiZpBA-UFWJ0bbck.woff2 new file mode 100644 index 0000000..49018f9 Binary files /dev/null and b/src/DjangoBlog/blog/static/blog/fonts/mem8YaGs126MiZpBA-UFWJ0bbck.woff2 differ diff --git a/src/DjangoBlog/blog/static/blog/fonts/mem8YaGs126MiZpBA-UFWZ0bbck.woff2 b/src/DjangoBlog/blog/static/blog/fonts/mem8YaGs126MiZpBA-UFWZ0bbck.woff2 new file mode 100644 index 0000000..a69a2ef Binary files /dev/null and b/src/DjangoBlog/blog/static/blog/fonts/mem8YaGs126MiZpBA-UFWZ0bbck.woff2 differ diff --git a/src/DjangoBlog/blog/static/blog/fonts/mem8YaGs126MiZpBA-UFWp0bbck.woff2 b/src/DjangoBlog/blog/static/blog/fonts/mem8YaGs126MiZpBA-UFWp0bbck.woff2 new file mode 100644 index 0000000..fb5fb99 Binary files /dev/null and b/src/DjangoBlog/blog/static/blog/fonts/mem8YaGs126MiZpBA-UFWp0bbck.woff2 differ diff --git a/src/DjangoBlog/blog/static/blog/fonts/memnYaGs126MiZpBA-UFUKWyV9hkIqOjjg.woff2 b/src/DjangoBlog/blog/static/blog/fonts/memnYaGs126MiZpBA-UFUKWyV9hkIqOjjg.woff2 new file mode 100644 index 0000000..db9a5bd Binary files /dev/null and b/src/DjangoBlog/blog/static/blog/fonts/memnYaGs126MiZpBA-UFUKWyV9hkIqOjjg.woff2 differ diff --git a/src/DjangoBlog/blog/static/blog/fonts/memnYaGs126MiZpBA-UFUKWyV9hlIqOjjg.woff2 b/src/DjangoBlog/blog/static/blog/fonts/memnYaGs126MiZpBA-UFUKWyV9hlIqOjjg.woff2 new file mode 100644 index 0000000..7a9e2e3 Binary files /dev/null and b/src/DjangoBlog/blog/static/blog/fonts/memnYaGs126MiZpBA-UFUKWyV9hlIqOjjg.woff2 differ diff --git a/src/DjangoBlog/blog/static/blog/fonts/memnYaGs126MiZpBA-UFUKWyV9hmIqOjjg.woff2 b/src/DjangoBlog/blog/static/blog/fonts/memnYaGs126MiZpBA-UFUKWyV9hmIqOjjg.woff2 new file mode 100644 index 0000000..a9d17c0 Binary files /dev/null and b/src/DjangoBlog/blog/static/blog/fonts/memnYaGs126MiZpBA-UFUKWyV9hmIqOjjg.woff2 differ diff --git a/src/DjangoBlog/blog/static/blog/fonts/memnYaGs126MiZpBA-UFUKWyV9hnIqOjjg.woff2 b/src/DjangoBlog/blog/static/blog/fonts/memnYaGs126MiZpBA-UFUKWyV9hnIqOjjg.woff2 new file mode 100644 index 0000000..b76038f Binary files /dev/null and b/src/DjangoBlog/blog/static/blog/fonts/memnYaGs126MiZpBA-UFUKWyV9hnIqOjjg.woff2 differ diff --git a/src/DjangoBlog/blog/static/blog/fonts/memnYaGs126MiZpBA-UFUKWyV9hoIqOjjg.woff2 b/src/DjangoBlog/blog/static/blog/fonts/memnYaGs126MiZpBA-UFUKWyV9hoIqOjjg.woff2 new file mode 100644 index 0000000..06a53d5 Binary files /dev/null and b/src/DjangoBlog/blog/static/blog/fonts/memnYaGs126MiZpBA-UFUKWyV9hoIqOjjg.woff2 differ diff --git a/src/DjangoBlog/blog/static/blog/fonts/memnYaGs126MiZpBA-UFUKWyV9hrIqM.woff2 b/src/DjangoBlog/blog/static/blog/fonts/memnYaGs126MiZpBA-UFUKWyV9hrIqM.woff2 new file mode 100644 index 0000000..94dc4e4 Binary files /dev/null and b/src/DjangoBlog/blog/static/blog/fonts/memnYaGs126MiZpBA-UFUKWyV9hrIqM.woff2 differ diff --git a/src/DjangoBlog/blog/static/blog/fonts/memnYaGs126MiZpBA-UFUKWyV9hvIqOjjg.woff2 b/src/DjangoBlog/blog/static/blog/fonts/memnYaGs126MiZpBA-UFUKWyV9hvIqOjjg.woff2 new file mode 100644 index 0000000..8197c39 Binary files /dev/null and b/src/DjangoBlog/blog/static/blog/fonts/memnYaGs126MiZpBA-UFUKWyV9hvIqOjjg.woff2 differ diff --git a/src/DjangoBlog/blog/static/blog/fonts/memnYaGs126MiZpBA-UFUKXGUdhkIqOjjg.woff2 b/src/DjangoBlog/blog/static/blog/fonts/memnYaGs126MiZpBA-UFUKXGUdhkIqOjjg.woff2 new file mode 100644 index 0000000..b9cd540 Binary files /dev/null and b/src/DjangoBlog/blog/static/blog/fonts/memnYaGs126MiZpBA-UFUKXGUdhkIqOjjg.woff2 differ diff --git a/src/DjangoBlog/blog/static/blog/fonts/memnYaGs126MiZpBA-UFUKXGUdhlIqOjjg.woff2 b/src/DjangoBlog/blog/static/blog/fonts/memnYaGs126MiZpBA-UFUKXGUdhlIqOjjg.woff2 new file mode 100644 index 0000000..fa2e381 Binary files /dev/null and b/src/DjangoBlog/blog/static/blog/fonts/memnYaGs126MiZpBA-UFUKXGUdhlIqOjjg.woff2 differ diff --git a/src/DjangoBlog/blog/static/blog/fonts/memnYaGs126MiZpBA-UFUKXGUdhmIqOjjg.woff2 b/src/DjangoBlog/blog/static/blog/fonts/memnYaGs126MiZpBA-UFUKXGUdhmIqOjjg.woff2 new file mode 100644 index 0000000..da3f7ec Binary files /dev/null and b/src/DjangoBlog/blog/static/blog/fonts/memnYaGs126MiZpBA-UFUKXGUdhmIqOjjg.woff2 differ diff --git a/src/DjangoBlog/blog/static/blog/fonts/memnYaGs126MiZpBA-UFUKXGUdhnIqOjjg.woff2 b/src/DjangoBlog/blog/static/blog/fonts/memnYaGs126MiZpBA-UFUKXGUdhnIqOjjg.woff2 new file mode 100644 index 0000000..0b42119 Binary files /dev/null and b/src/DjangoBlog/blog/static/blog/fonts/memnYaGs126MiZpBA-UFUKXGUdhnIqOjjg.woff2 differ diff --git a/src/DjangoBlog/blog/static/blog/fonts/memnYaGs126MiZpBA-UFUKXGUdhoIqOjjg.woff2 b/src/DjangoBlog/blog/static/blog/fonts/memnYaGs126MiZpBA-UFUKXGUdhoIqOjjg.woff2 new file mode 100644 index 0000000..36bdef1 Binary files /dev/null and b/src/DjangoBlog/blog/static/blog/fonts/memnYaGs126MiZpBA-UFUKXGUdhoIqOjjg.woff2 differ diff --git a/src/DjangoBlog/blog/static/blog/fonts/memnYaGs126MiZpBA-UFUKXGUdhrIqM.woff2 b/src/DjangoBlog/blog/static/blog/fonts/memnYaGs126MiZpBA-UFUKXGUdhrIqM.woff2 new file mode 100644 index 0000000..4b60ed4 Binary files /dev/null and b/src/DjangoBlog/blog/static/blog/fonts/memnYaGs126MiZpBA-UFUKXGUdhrIqM.woff2 differ diff --git a/src/DjangoBlog/blog/static/blog/fonts/memnYaGs126MiZpBA-UFUKXGUdhvIqOjjg.woff2 b/src/DjangoBlog/blog/static/blog/fonts/memnYaGs126MiZpBA-UFUKXGUdhvIqOjjg.woff2 new file mode 100644 index 0000000..d214090 Binary files /dev/null and b/src/DjangoBlog/blog/static/blog/fonts/memnYaGs126MiZpBA-UFUKXGUdhvIqOjjg.woff2 differ diff --git a/src/blog/static/blog/img/avatar.png b/src/DjangoBlog/blog/static/blog/img/avatar.png similarity index 100% rename from src/blog/static/blog/img/avatar.png rename to src/DjangoBlog/blog/static/blog/img/avatar.png diff --git a/src/blog/static/blog/img/icon-sn.svg b/src/DjangoBlog/blog/static/blog/img/icon-sn.svg similarity index 100% rename from src/blog/static/blog/img/icon-sn.svg rename to src/DjangoBlog/blog/static/blog/img/icon-sn.svg diff --git a/src/blog/static/blog/js/blog.js b/src/DjangoBlog/blog/static/blog/js/blog.js similarity index 100% rename from src/blog/static/blog/js/blog.js rename to src/DjangoBlog/blog/static/blog/js/blog.js diff --git a/src/blog/static/blog/js/html5.js b/src/DjangoBlog/blog/static/blog/js/html5.js similarity index 100% rename from src/blog/static/blog/js/html5.js rename to src/DjangoBlog/blog/static/blog/js/html5.js diff --git a/src/blog/static/blog/js/jquery-3.6.0.min.js b/src/DjangoBlog/blog/static/blog/js/jquery-3.6.0.min.js similarity index 100% rename from src/blog/static/blog/js/jquery-3.6.0.min.js rename to src/DjangoBlog/blog/static/blog/js/jquery-3.6.0.min.js diff --git a/src/blog/static/blog/js/navigation.js b/src/DjangoBlog/blog/static/blog/js/navigation.js similarity index 100% rename from src/blog/static/blog/js/navigation.js rename to src/DjangoBlog/blog/static/blog/js/navigation.js diff --git a/src/blog/static/blog/js/nprogress.js b/src/DjangoBlog/blog/static/blog/js/nprogress.js similarity index 100% rename from src/blog/static/blog/js/nprogress.js rename to src/DjangoBlog/blog/static/blog/js/nprogress.js diff --git a/src/DjangoBlog/blog/static/mathjax/js/mathjax-config.js b/src/DjangoBlog/blog/static/mathjax/js/mathjax-config.js new file mode 100644 index 0000000..158ba65 --- /dev/null +++ b/src/DjangoBlog/blog/static/mathjax/js/mathjax-config.js @@ -0,0 +1,21 @@ +$(function () { + MathJax.Hub.Config({ + showProcessingMessages: false, //关闭js加载过程信息 + messageStyle: "none", //不显示信息 + extensions: ["tex2jax.js"], jax: ["input/TeX", "output/HTML-CSS"], displayAlign: "left", tex2jax: { + inlineMath: [["$", "$"]], //行内公式选择$ + displayMath: [["$$", "$$"]], //段内公式选择$$ + skipTags: ['script', 'noscript', 'style', 'textarea', 'pre', 'code', 'a'], //避开某些标签 + }, "HTML-CSS": { + availableFonts: ["STIX", "TeX"], //可选字体 + showMathMenu: false //关闭右击菜单显示 + } + }); + // 识别范围 => 文章内容、评论内容标签 + const contentId = document.getElementById("content"); + const commentId = document.getElementById("comments"); + MathJax.Hub.Queue(["Typeset", MathJax.Hub, contentId, commentId]); +}) + + + diff --git a/src/blog/static/pygments/default.css b/src/DjangoBlog/blog/static/pygments/default.css similarity index 100% rename from src/blog/static/pygments/default.css rename to src/DjangoBlog/blog/static/pygments/default.css diff --git a/src/blog/templatetags/__init__.py b/src/DjangoBlog/blog/templatetags/__init__.py similarity index 100% rename from src/blog/templatetags/__init__.py rename to src/DjangoBlog/blog/templatetags/__init__.py diff --git a/src/DjangoBlog/blog/templatetags/__pycache__/__init__.cpython-310.pyc b/src/DjangoBlog/blog/templatetags/__pycache__/__init__.cpython-310.pyc new file mode 100644 index 0000000..803aff6 Binary files /dev/null and b/src/DjangoBlog/blog/templatetags/__pycache__/__init__.cpython-310.pyc differ diff --git a/src/DjangoBlog/blog/templatetags/__pycache__/blog_tags.cpython-310.pyc b/src/DjangoBlog/blog/templatetags/__pycache__/blog_tags.cpython-310.pyc new file mode 100644 index 0000000..9f0505e Binary files /dev/null and b/src/DjangoBlog/blog/templatetags/__pycache__/blog_tags.cpython-310.pyc differ diff --git a/src/blog/templatetags/blog_tags.py b/src/DjangoBlog/blog/templatetags/blog_tags.py similarity index 54% rename from src/blog/templatetags/blog_tags.py rename to src/DjangoBlog/blog/templatetags/blog_tags.py index 024f2c8..145da6e 100644 --- a/src/blog/templatetags/blog_tags.py +++ b/src/DjangoBlog/blog/templatetags/blog_tags.py @@ -20,21 +20,26 @@ from djangoblog.utils import get_current_site from oauth.models import OAuthUser from djangoblog.plugin_manage import hooks +# 获取当前模块的日志记录器 logger = logging.getLogger(__name__) +# 注册模板标签库,用于在Django模板中使用自定义标签和过滤器 register = template.Library() @register.simple_tag(takes_context=True) def head_meta(context): + # 应用过滤器到'head_meta',并标记为安全的HTML内容返回 return mark_safe(hooks.apply_filters('head_meta', '', context)) @register.simple_tag def timeformat(data): try: + # 按照设置的时间格式格式化日期时间数据 return data.strftime(settings.TIME_FORMAT) except Exception as e: + # 捕获异常并记录错误日志 logger.error(e) return "" @@ -42,6 +47,7 @@ def timeformat(data): @register.simple_tag def datetimeformat(data): try: + # 按照设置的日期时间格式格式化数据 return data.strftime(settings.DATE_TIME_FORMAT) except Exception as e: logger.error(e) @@ -51,80 +57,14 @@ def datetimeformat(data): @register.filter() @stringfilter def custom_markdown(content): - """ - 通用markdown过滤器,应用文章内容插件 - 主要用于文章内容处理 - """ - html_content = CommonMarkdown.get_markdown(content) - - # 然后应用插件过滤器优化HTML - from djangoblog.plugin_manage import hooks - from djangoblog.plugin_manage.hook_constants import ARTICLE_CONTENT_HOOK_NAME - optimized_html = hooks.apply_filters(ARTICLE_CONTENT_HOOK_NAME, html_content) - - return mark_safe(optimized_html) - - -@register.filter() -@stringfilter -def sidebar_markdown(content): - html_content = CommonMarkdown.get_markdown(content) - return mark_safe(html_content) - - -@register.simple_tag(takes_context=True) -def render_article_content(context, article, is_summary=False): - """ - 渲染文章内容,包含完整的上下文信息供插件使用 - - Args: - context: 模板上下文 - article: 文章对象 - is_summary: 是否为摘要模式(首页使用) - """ - if not article or not hasattr(article, 'body'): - return '' - - # 先转换Markdown为HTML - html_content = CommonMarkdown.get_markdown(article.body) - - # 如果是摘要模式,先截断内容再应用插件 - if is_summary: - # 截断HTML内容到合适的长度(约300字符) - from django.utils.html import strip_tags - from django.template.defaultfilters import truncatechars - - # 先去除HTML标签,截断纯文本,然后重新转换为HTML - plain_text = strip_tags(html_content) - truncated_text = truncatechars(plain_text, 300) - - # 重新转换截断后的文本为HTML(简化版,避免复杂的插件处理) - html_content = CommonMarkdown.get_markdown(truncated_text) - - # 然后应用插件过滤器,传递完整的上下文 - from djangoblog.plugin_manage import hooks - from djangoblog.plugin_manage.hook_constants import ARTICLE_CONTENT_HOOK_NAME - - # 获取request对象 - request = context.get('request') - - # 应用所有文章内容相关的插件 - # 注意:摘要模式下某些插件(如版权声明)可能不适用 - optimized_html = hooks.apply_filters( - ARTICLE_CONTENT_HOOK_NAME, - html_content, - article=article, - request=request, - context=context, - is_summary=is_summary # 传递摘要标志,插件可以据此调整行为 - ) - - return mark_safe(optimized_html) + # 将内容用自定义的Markdown解析器转换为HTML,并标记为安全 + return mark_safe(CommonMarkdown.get_markdown(content)) @register.simple_tag def get_markdown_toc(content): from djangoblog.utils import CommonMarkdown + # 获取Markdown内容的目录 body, toc = CommonMarkdown.get_markdown_with_toc(content) return mark_safe(toc) @@ -132,6 +72,7 @@ def get_markdown_toc(content): @register.filter() @stringfilter def comment_markdown(content): + # 先将评论内容用Markdown解析,再进行HTML清理,最后标记为安全 content = CommonMarkdown.get_markdown(content) return mark_safe(sanitize_html(content)) @@ -146,7 +87,9 @@ def truncatechars_content(content): """ from django.template.defaultfilters import truncatechars_html from djangoblog.utils import get_blog_setting + # 获取博客设置 blogsetting = get_blog_setting() + # 按照设置的文章摘要长度截断HTML内容 return truncatechars_html(content, blogsetting.article_sub_length) @@ -154,7 +97,7 @@ def truncatechars_content(content): @stringfilter def truncate(content): from django.utils.html import strip_tags - + # 去除HTML标签后截断内容,取前150个字符 return strip_tags(content)[:150] @@ -165,11 +108,14 @@ def load_breadcrumb(article): :param article: :return: """ + # 获取文章的分类树 names = article.get_category_tree() from djangoblog.utils import get_blog_setting blogsetting = get_blog_setting() site = get_current_site().domain + # 把网站名称加入面包屑列表 names.append((blogsetting.site_name, '/')) + # 反转列表,调整面包屑顺序 names = names[::-1] return { @@ -186,9 +132,11 @@ def load_articletags(article): :param article: :return: """ + # 获取文章的所有标签 tags = article.tags.all() tags_list = [] for tag in tags: + # 为每个标签生成URL、文章数量等信息,并随机选择一个引导类颜色 url = tag.get_absolute_url() count = tag.get_article_count() tags_list.append(( @@ -205,6 +153,7 @@ def load_sidebar(user, linktype): 加载侧边栏 :return: """ + # 尝试从缓存获取侧边栏数据 value = cache.get("sidebar" + linktype) if value: value['user'] = user @@ -213,16 +162,23 @@ def load_sidebar(user, linktype): logger.info('load sidebar') from djangoblog.utils import get_blog_setting blogsetting = get_blog_setting() + # 获取最近的文章 recent_articles = Article.objects.filter( status='p')[:blogsetting.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')[:blogsetting.sidebar_article_count] + # 获取按月份归档的日期 dates = Article.objects.datetimes('creation_time', 'month', order='DESC') + # 获取符合显示类型的链接 links = Links.objects.filter(is_enable=True).filter( Q(show_type=str(linktype)) | Q(show_type=LinkShowType.A)) + # 获取最近的评论 commment_list = Comment.objects.filter(is_enable=True).order_by( '-id')[:blogsetting.sidebar_comment_count] # 标签云 计算字体大小 @@ -253,6 +209,7 @@ def load_sidebar(user, linktype): 'sidebar_tags': sidebar_tags, 'extra_sidebars': extra_sidebars } + # 设置侧边栏缓存,有效期3小时 cache.set("sidebar" + linktype, value, 60 * 60 * 60 * 3) logger.info('set sidebar cache.key:{key}'.format(key="sidebar" + linktype)) value['user'] = user @@ -279,9 +236,11 @@ def load_pagination_info(page_obj, page_type, tag_name): if page_type == '': if page_obj.has_next(): next_number = page_obj.next_page_number() + # 生成下一页的URL next_url = reverse('blog:index_page', kwargs={'page': next_number}) if page_obj.has_previous(): previous_number = page_obj.previous_page_number() + # 生成上一页的URL previous_url = reverse( 'blog:index_page', kwargs={ 'page': previous_number}) @@ -360,49 +319,42 @@ def load_article_detail(article, isindex, user): } -# 返回用户头像URL -# 模板使用方法: {{ email|gravatar_url:150 }} +# return only the URL of the gravatar +# TEMPLATE USE: {{ email|gravatar_url:150 }} @register.filter def gravatar_url(email, size=40): - """获得用户头像 - 优先使用OAuth头像,否则使用默认头像""" - cachekey = 'avatar/' + email + """获得gravatar头像""" + cachekey = 'gravatat/' + email + # 尝试从缓存获取头像URL url = cache.get(cachekey) if url: return url - - # 检查OAuth用户是否有自定义头像 - usermodels = OAuthUser.objects.filter(email=email) - if usermodels: - # 过滤出有头像的用户 - users_with_picture = list(filter(lambda x: x.picture is not None, usermodels)) - if users_with_picture: - # 获取默认头像路径用于比较 - default_avatar_path = static('blog/img/avatar.png') - - # 优先选择非默认头像的用户,否则选择第一个 - non_default_users = [u for u in users_with_picture if u.picture != default_avatar_path and not u.picture.endswith('/avatar.png')] - selected_user = non_default_users[0] if non_default_users else users_with_picture[0] - - url = selected_user.picture - cache.set(cachekey, url, 60 * 60 * 24) # 缓存24小时 - - avatar_type = 'non-default' if non_default_users else 'default' - logger.info('Using {} OAuth avatar for {} from {}'.format(avatar_type, email, selected_user.type)) - return url - - # 使用默认头像 - url = static('blog/img/avatar.png') - cache.set(cachekey, url, 60 * 60 * 24) # 缓存24小时 - logger.info('Using default avatar for {}'.format(email)) - return url + else: + # 查找对应的OAuth用户 + 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 = static('blog/img/avatar.png') + + url = "https://www.gravatar.com/avatar/%s?%s" % (hashlib.md5( + email.lower()).hexdigest(), urllib.parse.urlencode({'d': default, 's': str(size)})) + # 设置头像URL缓存,有效期10分钟 + cache.set(cachekey, url, 60 * 10) + logger.info('set gravatar cache.key:{key}'.format(key=cachekey)) + return url @register.filter def gravatar(email, size=40): - """获得用户头像HTML标签""" + """获得gravatar头像""" + # 获取头像URL并生成img标签,标记为安全的HTML url = gravatar_url(email, size) return mark_safe( - '用户头像' % + '' % (url, size, size)) @@ -414,141 +366,12 @@ def query(qs, **kwargs): ... {% endfor %} """ + # 对查询集进行过滤 return qs.filter(**kwargs) @register.filter def addstr(arg1, arg2): """concatenate arg1 & arg2""" - return str(arg1) + str(arg2) - - -# === 插件系统模板标签 === - -@register.simple_tag(takes_context=True) -def render_plugin_widgets(context, position, **kwargs): - """ - 渲染指定位置的所有插件组件 - - Args: - context: 模板上下文 - position: 位置标识 - **kwargs: 传递给插件的额外参数 - - Returns: - 按优先级排序的所有插件HTML内容 - """ - from djangoblog.plugin_manage.loader import get_loaded_plugins - - widgets = [] - - for plugin in get_loaded_plugins(): - try: - widget_data = plugin.render_position_widget( - position=position, - context=context, - **kwargs - ) - if widget_data: - widgets.append(widget_data) - except Exception as e: - logger.error(f"Error rendering widget from plugin {plugin.PLUGIN_NAME}: {e}") - - # 按优先级排序(数字越小优先级越高) - widgets.sort(key=lambda x: x['priority']) - - # 合并HTML内容 - html_parts = [widget['html'] for widget in widgets] - return mark_safe(''.join(html_parts)) - - -@register.simple_tag(takes_context=True) -def plugin_head_resources(context): - """渲染所有插件的head资源(仅自定义HTML,CSS已集成到压缩系统)""" - from djangoblog.plugin_manage.loader import get_loaded_plugins - - resources = [] - - for plugin in get_loaded_plugins(): - try: - # 只处理自定义head HTML(CSS文件已通过压缩系统处理) - head_html = plugin.get_head_html(context) - if head_html: - resources.append(head_html) - - except Exception as e: - logger.error(f"Error loading head resources from plugin {plugin.PLUGIN_NAME}: {e}") - - return mark_safe('\n'.join(resources)) - - -@register.simple_tag(takes_context=True) -def plugin_body_resources(context): - """渲染所有插件的body资源(仅自定义HTML,JS已集成到压缩系统)""" - from djangoblog.plugin_manage.loader import get_loaded_plugins - - resources = [] - - for plugin in get_loaded_plugins(): - try: - # 只处理自定义body HTML(JS文件已通过压缩系统处理) - body_html = plugin.get_body_html(context) - if body_html: - resources.append(body_html) - - except Exception as e: - logger.error(f"Error loading body resources from plugin {plugin.PLUGIN_NAME}: {e}") - - return mark_safe('\n'.join(resources)) - - -@register.inclusion_tag('plugins/css_includes.html') -def plugin_compressed_css(): - """插件CSS压缩包含模板""" - from djangoblog.plugin_manage.loader import get_loaded_plugins - - css_files = [] - for plugin in get_loaded_plugins(): - for css_file in plugin.get_css_files(): - css_url = plugin.get_static_url(css_file) - css_files.append(css_url) - - return {'css_files': css_files} - - -@register.inclusion_tag('plugins/js_includes.html') -def plugin_compressed_js(): - """插件JS压缩包含模板""" - from djangoblog.plugin_manage.loader import get_loaded_plugins - - js_files = [] - for plugin in get_loaded_plugins(): - for js_file in plugin.get_js_files(): - js_url = plugin.get_static_url(js_file) - js_files.append(js_url) - - return {'js_files': js_files} - - - - -@register.simple_tag(takes_context=True) -def plugin_widget(context, plugin_name, widget_type='default', **kwargs): - """ - 渲染指定插件的组件 - - 使用方式: - {% plugin_widget 'article_recommendation' 'bottom' article=article count=5 %} - """ - from djangoblog.plugin_manage.loader import get_plugin_by_slug - - plugin = get_plugin_by_slug(plugin_name) - if plugin and hasattr(plugin, 'render_template'): - try: - widget_context = {**context.flatten(), **kwargs} - template_name = f"{widget_type}.html" - return mark_safe(plugin.render_template(template_name, widget_context)) - except Exception as e: - logger.error(f"Error rendering plugin widget {plugin_name}.{widget_type}: {e}") - - return "" \ No newline at end of file + # 连接两个字符串 + return str(arg1) + str(arg2) \ No newline at end of file diff --git a/src/blog/tests.py b/src/DjangoBlog/blog/tests.py similarity index 100% rename from src/blog/tests.py rename to src/DjangoBlog/blog/tests.py diff --git a/src/blog/urls.py b/src/DjangoBlog/blog/urls.py similarity index 68% rename from src/blog/urls.py rename to src/DjangoBlog/blog/urls.py index f11f813..adf2703 100644 --- a/src/blog/urls.py +++ b/src/DjangoBlog/blog/urls.py @@ -4,83 +4,57 @@ from django.views.decorators.cache import cache_page from . import views app_name = "blog" - urlpatterns = [ - # 首页,显示博客文章列表 path( r'', views.IndexView.as_view(), name='index'), - - # 分页的首页,显示指定页码的博客文章列表 path( r'page//', views.IndexView.as_view(), name='index_page'), - - # 文章详情页,通过年份、月份、日期和文章ID访问 path( r'article////.html', views.ArticleDetailView.as_view(), name='detailbyid'), - - # 分类详情页,通过分类名称访问 path( r'category/.html', views.CategoryDetailView.as_view(), name='category_detail'), - - # 分类详情页的分页,通过分类名称和页码访问 path( r'category//.html', views.CategoryDetailView.as_view(), name='category_detail_page'), - - # 作者详情页,通过作者名称访问 path( r'author/.html', views.AuthorDetailView.as_view(), name='author_detail'), - - # 作者详情页的分页,通过作者名称和页码访问 path( r'author//.html', views.AuthorDetailView.as_view(), name='author_detail_page'), - - # 标签详情页,通过标签名称访问 path( r'tag/.html', views.TagDetailView.as_view(), name='tag_detail'), - - # 标签详情页的分页,通过标签名称和页码访问 path( r'tag//.html', views.TagDetailView.as_view(), name='tag_detail_page'), - - # 归档页面,显示所有文章的归档信息,缓存时间为1小时 path( 'archives.html', cache_page( - 60 * 60)( # 缓存时间:60分钟 * 60秒 + 60 * 60)( views.ArchivesView.as_view()), name='archives'), - - # 友情链接页面 path( 'links.html', views.LinkListView.as_view(), name='links'), - - # 文件上传接口 path( r'upload', views.fileupload, name='upload'), - - # 清理缓存接口 path( r'clean', views.clean_cache_view, diff --git a/src/blog/views.py b/src/DjangoBlog/blog/views.py similarity index 90% rename from src/blog/views.py rename to src/DjangoBlog/blog/views.py index 73bdffb..d5dc7ec 100644 --- a/src/blog/views.py +++ b/src/DjangoBlog/blog/views.py @@ -23,7 +23,7 @@ from djangoblog.utils import cache, get_blog_setting, get_sha256 logger = logging.getLogger(__name__) -# 文章列表视图基类 + class ArticleListView(ListView): # template_name属性用于指定使用哪个模板进行渲染 template_name = 'blog/article_index.html' @@ -38,7 +38,6 @@ class ArticleListView(ListView): link_type = LinkShowType.L def get_view_cache_key(self): - # 获取视图缓存的键 return self.request.get['pages'] @property @@ -62,7 +61,7 @@ class ArticleListView(ListView): def get_queryset_from_cache(self, cache_key): ''' - 从缓存中获取页面数据,如果不存在则设置缓存 + 缓存页面数据 :param cache_key: 缓存key :return: ''' @@ -78,7 +77,7 @@ class ArticleListView(ListView): def get_queryset(self): ''' - 重写默认方法,从缓存中获取数据 + 重写默认,从缓存获取数据 :return: ''' key = self.get_queryset_cache_key() @@ -90,10 +89,9 @@ class ArticleListView(ListView): return super(ArticleListView, self).get_context_data(**kwargs) -# 首页视图 class IndexView(ArticleListView): ''' - 首页文章列表 + 首页 ''' # 友情链接类型 link_type = LinkShowType.I @@ -107,7 +105,6 @@ class IndexView(ArticleListView): return cache_key -# 文章详情视图 class ArticleDetailView(DetailView): ''' 文章详情页面 @@ -155,20 +152,18 @@ class ArticleDetailView(DetailView): context = super(ArticleDetailView, self).get_context_data(**kwargs) article = self.object - - # 触发文章详情加载钩子,让插件可以添加额外的上下文数据 - from djangoblog.plugin_manage.hook_constants import ARTICLE_DETAIL_LOAD - hooks.run_action(ARTICLE_DETAIL_LOAD, article=article, context=context, request=self.request) - # Action Hook, 通知插件"文章详情已获取" hooks.run_action('after_article_body_get', article=article, request=self.request) + # # Filter Hook, 允许插件修改文章正文 + article.body = hooks.apply_filters(ARTICLE_CONTENT_HOOK_NAME, article.body, article=article, + request=self.request) + return context -# 分类目录视图 class CategoryDetailView(ArticleListView): ''' - 分类目录文章列表 + 分类目录列表 ''' page_type = "分类目录归档" @@ -205,10 +200,9 @@ class CategoryDetailView(ArticleListView): return super(CategoryDetailView, self).get_context_data(**kwargs) -# 作者详情视图 class AuthorDetailView(ArticleListView): ''' - 作者文章归档页面 + 作者详情页 ''' page_type = '作者文章归档' @@ -232,10 +226,9 @@ class AuthorDetailView(ArticleListView): return super(AuthorDetailView, self).get_context_data(**kwargs) -# 标签详情视图 class TagDetailView(ArticleListView): ''' - 标签文章列表页面 + 标签列表页面 ''' page_type = '分类标签归档' @@ -265,7 +258,6 @@ class TagDetailView(ArticleListView): return super(TagDetailView, self).get_context_data(**kwargs) -# 文章归档视图 class ArchivesView(ArticleListView): ''' 文章归档页面 @@ -283,11 +275,7 @@ class ArchivesView(ArticleListView): return cache_key -# 友情链接视图 class LinkListView(ListView): - ''' - 友情链接页面 - ''' model = Links template_name = 'blog/links_list.html' @@ -295,11 +283,7 @@ class LinkListView(ListView): return Links.objects.filter(is_enable=True) -# 搜索视图 class EsSearchView(SearchView): - ''' - 自定义搜索视图 - ''' def get_context(self): paginator, page = self.build_page() context = { @@ -316,11 +300,12 @@ class EsSearchView(SearchView): return context -# 文件上传视图 @csrf_exempt def fileupload(request): """ - 提供图床功能的文件上传接口 + 该方法需自己写调用端来上传图片,该方法仅提供图床功能 + :param request: + :return: """ if request.method == 'POST': sign = request.GET.get('sign', None) @@ -355,11 +340,10 @@ def fileupload(request): return HttpResponse("only for post") -# 自定义错误页面视图 -def page_not_found_view(request, exception, template_name='blog/error_page.html'): - ''' - 404 页面未找到错误处理 - ''' +def page_not_found_view( + request, + exception, + template_name='blog/error_page.html'): if exception: logger.error(exception) url = request.get_full_path() @@ -371,9 +355,6 @@ def page_not_found_view(request, exception, template_name='blog/error_page.html' def server_error_view(request, template_name='blog/error_page.html'): - ''' - 500 服务器错误处理 - ''' return render(request, template_name, {'message': _('Sorry, the server is busy, please click the home page to see other?'), @@ -381,10 +362,10 @@ def server_error_view(request, template_name='blog/error_page.html'): status=500) -def permission_denied_view(request, exception, template_name='blog/error_page.html'): - ''' - 403 权限不足错误处理 - ''' +def permission_denied_view( + request, + exception, + template_name='blog/error_page.html'): if exception: logger.error(exception) return render( @@ -393,10 +374,6 @@ def permission_denied_view(request, exception, template_name='blog/error_page.ht 'statuscode': '403'}, status=403) -# 清理缓存视图 def clean_cache_view(request): - ''' - 清理缓存 - ''' cache.clear() return HttpResponse('ok') diff --git a/src/DjangoBlog/collectedstatic/CACHE/css/output.695179b21c97.css b/src/DjangoBlog/collectedstatic/CACHE/css/output.695179b21c97.css new file mode 100644 index 0000000..fd6fec3 --- /dev/null +++ b/src/DjangoBlog/collectedstatic/CACHE/css/output.695179b21c97.css @@ -0,0 +1,13 @@ +/*! + * Bootstrap v3.3.7 (http://getbootstrap.com) + * Copyright 2011-2016 Twitter, Inc. + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) + *//*! normalize.css v3.0.3 | MIT License | github.com/necolas/normalize.css */html{font-family:sans-serif;-webkit-text-size-adjust:100%;-ms-text-size-adjust:100%}body{margin:0}article,aside,details,figcaption,figure,footer,header,hgroup,main,menu,nav,section,summary{display:block}audio,canvas,progress,video{display:inline-block;vertical-align:baseline}audio:not([controls]){display:none;height:0}[hidden],template{display:none}a{background-color:transparent}a:active,a:hover{outline:0}abbr[title]{border-bottom:1px dotted}b,strong{font-weight:700}dfn{font-style:italic}h1{margin:.67em 0;font-size:2em}mark{color:#000;background:#ff0}small{font-size:80%}sub,sup{position:relative;font-size:75%;line-height:0;vertical-align:baseline}sup{top:-.5em}sub{bottom:-.25em}img{border:0}svg:not(:root){overflow:hidden}figure{margin:1em 40px}hr{height:0;-webkit-box-sizing:content-box;-moz-box-sizing:content-box;box-sizing:content-box}pre{overflow:auto}code,kbd,pre,samp{font-family:monospace,monospace;font-size:1em}button,input,optgroup,select,textarea{margin:0;font:inherit;color:inherit}button{overflow:visible}button,select{text-transform:none}button,html input[type=button],input[type=reset],input[type=submit]{-webkit-appearance:button;cursor:pointer}button[disabled],html input[disabled]{cursor:default}button::-moz-focus-inner,input::-moz-focus-inner{padding:0;border:0}input{line-height:normal}input[type=checkbox],input[type=radio]{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;padding:0}input[type=number]::-webkit-inner-spin-button,input[type=number]::-webkit-outer-spin-button{height:auto}input[type=search]{-webkit-box-sizing:content-box;-moz-box-sizing:content-box;box-sizing:content-box;-webkit-appearance:textfield}input[type=search]::-webkit-search-cancel-button,input[type=search]::-webkit-search-decoration{-webkit-appearance:none}fieldset{padding:.35em .625em .75em;margin:0 2px;border:1px solid silver}legend{padding:0;border:0}textarea{overflow:auto}optgroup{font-weight:700}table{border-spacing:0;border-collapse:collapse}td,th{padding:0}/*! Source: https://github.com/h5bp/html5-boilerplate/blob/master/src/css/main.css */@media print{*,:after,:before{color:#000!important;text-shadow:none!important;background:0 0!important;-webkit-box-shadow:none!important;box-shadow:none!important}a,a:visited{text-decoration:underline}a[href]:after{content:" (" attr(href) ")"}abbr[title]:after{content:" (" attr(title) ")"}a[href^="javascript:"]:after,a[href^="#"]:after{content:""}blockquote,pre{border:1px solid #999;page-break-inside:avoid}thead{display:table-header-group}img,tr{page-break-inside:avoid}img{max-width:100%!important}h2,h3,p{orphans:3;widows:3}h2,h3{page-break-after:avoid}.navbar{display:none}.btn>.caret,.dropup>.btn>.caret{border-top-color:#000!important}.label{border:1px solid #000}.table{border-collapse:collapse!important}.table td,.table th{background-color:#fff!important}.table-bordered td,.table-bordered th{border:1px solid #ddd!important}}@font-face{font-family:'Glyphicons Halflings';src:url(/static/assets/fonts/glyphicons-halflings-regular.eot);src:url(/static/assets/fonts/glyphicons-halflings-regular.eot?#iefix) format('embedded-opentype'),url(/static/assets/fonts/glyphicons-halflings-regular.woff2) format('woff2'),url(/static/assets/fonts/glyphicons-halflings-regular.woff) format('woff'),url(/static/assets/fonts/glyphicons-halflings-regular.ttf) format('truetype'),url(/static/assets/fonts/glyphicons-halflings-regular.svg#glyphicons_halflingsregular) format('svg')}.glyphicon{position:relative;top:1px;display:inline-block;font-family:'Glyphicons Halflings';font-style:normal;font-weight:400;line-height:1;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.glyphicon-asterisk:before{content:"\002a"}.glyphicon-plus:before{content:"\002b"}.glyphicon-eur:before,.glyphicon-euro:before{content:"\20ac"}.glyphicon-minus:before{content:"\2212"}.glyphicon-cloud:before{content:"\2601"}.glyphicon-envelope:before{content:"\2709"}.glyphicon-pencil:before{content:"\270f"}.glyphicon-glass:before{content:"\e001"}.glyphicon-music:before{content:"\e002"}.glyphicon-search:before{content:"\e003"}.glyphicon-heart:before{content:"\e005"}.glyphicon-star:before{content:"\e006"}.glyphicon-star-empty:before{content:"\e007"}.glyphicon-user:before{content:"\e008"}.glyphicon-film:before{content:"\e009"}.glyphicon-th-large:before{content:"\e010"}.glyphicon-th:before{content:"\e011"}.glyphicon-th-list:before{content:"\e012"}.glyphicon-ok:before{content:"\e013"}.glyphicon-remove:before{content:"\e014"}.glyphicon-zoom-in:before{content:"\e015"}.glyphicon-zoom-out:before{content:"\e016"}.glyphicon-off:before{content:"\e017"}.glyphicon-signal:before{content:"\e018"}.glyphicon-cog:before{content:"\e019"}.glyphicon-trash:before{content:"\e020"}.glyphicon-home:before{content:"\e021"}.glyphicon-file:before{content:"\e022"}.glyphicon-time:before{content:"\e023"}.glyphicon-road:before{content:"\e024"}.glyphicon-download-alt:before{content:"\e025"}.glyphicon-download:before{content:"\e026"}.glyphicon-upload:before{content:"\e027"}.glyphicon-inbox:before{content:"\e028"}.glyphicon-play-circle:before{content:"\e029"}.glyphicon-repeat:before{content:"\e030"}.glyphicon-refresh:before{content:"\e031"}.glyphicon-list-alt:before{content:"\e032"}.glyphicon-lock:before{content:"\e033"}.glyphicon-flag:before{content:"\e034"}.glyphicon-headphones:before{content:"\e035"}.glyphicon-volume-off:before{content:"\e036"}.glyphicon-volume-down:before{content:"\e037"}.glyphicon-volume-up:before{content:"\e038"}.glyphicon-qrcode:before{content:"\e039"}.glyphicon-barcode:before{content:"\e040"}.glyphicon-tag:before{content:"\e041"}.glyphicon-tags:before{content:"\e042"}.glyphicon-book:before{content:"\e043"}.glyphicon-bookmark:before{content:"\e044"}.glyphicon-print:before{content:"\e045"}.glyphicon-camera:before{content:"\e046"}.glyphicon-font:before{content:"\e047"}.glyphicon-bold:before{content:"\e048"}.glyphicon-italic:before{content:"\e049"}.glyphicon-text-height:before{content:"\e050"}.glyphicon-text-width:before{content:"\e051"}.glyphicon-align-left:before{content:"\e052"}.glyphicon-align-center:before{content:"\e053"}.glyphicon-align-right:before{content:"\e054"}.glyphicon-align-justify:before{content:"\e055"}.glyphicon-list:before{content:"\e056"}.glyphicon-indent-left:before{content:"\e057"}.glyphicon-indent-right:before{content:"\e058"}.glyphicon-facetime-video:before{content:"\e059"}.glyphicon-picture:before{content:"\e060"}.glyphicon-map-marker:before{content:"\e062"}.glyphicon-adjust:before{content:"\e063"}.glyphicon-tint:before{content:"\e064"}.glyphicon-edit:before{content:"\e065"}.glyphicon-share:before{content:"\e066"}.glyphicon-check:before{content:"\e067"}.glyphicon-move:before{content:"\e068"}.glyphicon-step-backward:before{content:"\e069"}.glyphicon-fast-backward:before{content:"\e070"}.glyphicon-backward:before{content:"\e071"}.glyphicon-play:before{content:"\e072"}.glyphicon-pause:before{content:"\e073"}.glyphicon-stop:before{content:"\e074"}.glyphicon-forward:before{content:"\e075"}.glyphicon-fast-forward:before{content:"\e076"}.glyphicon-step-forward:before{content:"\e077"}.glyphicon-eject:before{content:"\e078"}.glyphicon-chevron-left:before{content:"\e079"}.glyphicon-chevron-right:before{content:"\e080"}.glyphicon-plus-sign:before{content:"\e081"}.glyphicon-minus-sign:before{content:"\e082"}.glyphicon-remove-sign:before{content:"\e083"}.glyphicon-ok-sign:before{content:"\e084"}.glyphicon-question-sign:before{content:"\e085"}.glyphicon-info-sign:before{content:"\e086"}.glyphicon-screenshot:before{content:"\e087"}.glyphicon-remove-circle:before{content:"\e088"}.glyphicon-ok-circle:before{content:"\e089"}.glyphicon-ban-circle:before{content:"\e090"}.glyphicon-arrow-left:before{content:"\e091"}.glyphicon-arrow-right:before{content:"\e092"}.glyphicon-arrow-up:before{content:"\e093"}.glyphicon-arrow-down:before{content:"\e094"}.glyphicon-share-alt:before{content:"\e095"}.glyphicon-resize-full:before{content:"\e096"}.glyphicon-resize-small:before{content:"\e097"}.glyphicon-exclamation-sign:before{content:"\e101"}.glyphicon-gift:before{content:"\e102"}.glyphicon-leaf:before{content:"\e103"}.glyphicon-fire:before{content:"\e104"}.glyphicon-eye-open:before{content:"\e105"}.glyphicon-eye-close:before{content:"\e106"}.glyphicon-warning-sign:before{content:"\e107"}.glyphicon-plane:before{content:"\e108"}.glyphicon-calendar:before{content:"\e109"}.glyphicon-random:before{content:"\e110"}.glyphicon-comment:before{content:"\e111"}.glyphicon-magnet:before{content:"\e112"}.glyphicon-chevron-up:before{content:"\e113"}.glyphicon-chevron-down:before{content:"\e114"}.glyphicon-retweet:before{content:"\e115"}.glyphicon-shopping-cart:before{content:"\e116"}.glyphicon-folder-close:before{content:"\e117"}.glyphicon-folder-open:before{content:"\e118"}.glyphicon-resize-vertical:before{content:"\e119"}.glyphicon-resize-horizontal:before{content:"\e120"}.glyphicon-hdd:before{content:"\e121"}.glyphicon-bullhorn:before{content:"\e122"}.glyphicon-bell:before{content:"\e123"}.glyphicon-certificate:before{content:"\e124"}.glyphicon-thumbs-up:before{content:"\e125"}.glyphicon-thumbs-down:before{content:"\e126"}.glyphicon-hand-right:before{content:"\e127"}.glyphicon-hand-left:before{content:"\e128"}.glyphicon-hand-up:before{content:"\e129"}.glyphicon-hand-down:before{content:"\e130"}.glyphicon-circle-arrow-right:before{content:"\e131"}.glyphicon-circle-arrow-left:before{content:"\e132"}.glyphicon-circle-arrow-up:before{content:"\e133"}.glyphicon-circle-arrow-down:before{content:"\e134"}.glyphicon-globe:before{content:"\e135"}.glyphicon-wrench:before{content:"\e136"}.glyphicon-tasks:before{content:"\e137"}.glyphicon-filter:before{content:"\e138"}.glyphicon-briefcase:before{content:"\e139"}.glyphicon-fullscreen:before{content:"\e140"}.glyphicon-dashboard:before{content:"\e141"}.glyphicon-paperclip:before{content:"\e142"}.glyphicon-heart-empty:before{content:"\e143"}.glyphicon-link:before{content:"\e144"}.glyphicon-phone:before{content:"\e145"}.glyphicon-pushpin:before{content:"\e146"}.glyphicon-usd:before{content:"\e148"}.glyphicon-gbp:before{content:"\e149"}.glyphicon-sort:before{content:"\e150"}.glyphicon-sort-by-alphabet:before{content:"\e151"}.glyphicon-sort-by-alphabet-alt:before{content:"\e152"}.glyphicon-sort-by-order:before{content:"\e153"}.glyphicon-sort-by-order-alt:before{content:"\e154"}.glyphicon-sort-by-attributes:before{content:"\e155"}.glyphicon-sort-by-attributes-alt:before{content:"\e156"}.glyphicon-unchecked:before{content:"\e157"}.glyphicon-expand:before{content:"\e158"}.glyphicon-collapse-down:before{content:"\e159"}.glyphicon-collapse-up:before{content:"\e160"}.glyphicon-log-in:before{content:"\e161"}.glyphicon-flash:before{content:"\e162"}.glyphicon-log-out:before{content:"\e163"}.glyphicon-new-window:before{content:"\e164"}.glyphicon-record:before{content:"\e165"}.glyphicon-save:before{content:"\e166"}.glyphicon-open:before{content:"\e167"}.glyphicon-saved:before{content:"\e168"}.glyphicon-import:before{content:"\e169"}.glyphicon-export:before{content:"\e170"}.glyphicon-send:before{content:"\e171"}.glyphicon-floppy-disk:before{content:"\e172"}.glyphicon-floppy-saved:before{content:"\e173"}.glyphicon-floppy-remove:before{content:"\e174"}.glyphicon-floppy-save:before{content:"\e175"}.glyphicon-floppy-open:before{content:"\e176"}.glyphicon-credit-card:before{content:"\e177"}.glyphicon-transfer:before{content:"\e178"}.glyphicon-cutlery:before{content:"\e179"}.glyphicon-header:before{content:"\e180"}.glyphicon-compressed:before{content:"\e181"}.glyphicon-earphone:before{content:"\e182"}.glyphicon-phone-alt:before{content:"\e183"}.glyphicon-tower:before{content:"\e184"}.glyphicon-stats:before{content:"\e185"}.glyphicon-sd-video:before{content:"\e186"}.glyphicon-hd-video:before{content:"\e187"}.glyphicon-subtitles:before{content:"\e188"}.glyphicon-sound-stereo:before{content:"\e189"}.glyphicon-sound-dolby:before{content:"\e190"}.glyphicon-sound-5-1:before{content:"\e191"}.glyphicon-sound-6-1:before{content:"\e192"}.glyphicon-sound-7-1:before{content:"\e193"}.glyphicon-copyright-mark:before{content:"\e194"}.glyphicon-registration-mark:before{content:"\e195"}.glyphicon-cloud-download:before{content:"\e197"}.glyphicon-cloud-upload:before{content:"\e198"}.glyphicon-tree-conifer:before{content:"\e199"}.glyphicon-tree-deciduous:before{content:"\e200"}.glyphicon-cd:before{content:"\e201"}.glyphicon-save-file:before{content:"\e202"}.glyphicon-open-file:before{content:"\e203"}.glyphicon-level-up:before{content:"\e204"}.glyphicon-copy:before{content:"\e205"}.glyphicon-paste:before{content:"\e206"}.glyphicon-alert:before{content:"\e209"}.glyphicon-equalizer:before{content:"\e210"}.glyphicon-king:before{content:"\e211"}.glyphicon-queen:before{content:"\e212"}.glyphicon-pawn:before{content:"\e213"}.glyphicon-bishop:before{content:"\e214"}.glyphicon-knight:before{content:"\e215"}.glyphicon-baby-formula:before{content:"\e216"}.glyphicon-tent:before{content:"\26fa"}.glyphicon-blackboard:before{content:"\e218"}.glyphicon-bed:before{content:"\e219"}.glyphicon-apple:before{content:"\f8ff"}.glyphicon-erase:before{content:"\e221"}.glyphicon-hourglass:before{content:"\231b"}.glyphicon-lamp:before{content:"\e223"}.glyphicon-duplicate:before{content:"\e224"}.glyphicon-piggy-bank:before{content:"\e225"}.glyphicon-scissors:before{content:"\e226"}.glyphicon-bitcoin:before{content:"\e227"}.glyphicon-btc:before{content:"\e227"}.glyphicon-xbt:before{content:"\e227"}.glyphicon-yen:before{content:"\00a5"}.glyphicon-jpy:before{content:"\00a5"}.glyphicon-ruble:before{content:"\20bd"}.glyphicon-rub:before{content:"\20bd"}.glyphicon-scale:before{content:"\e230"}.glyphicon-ice-lolly:before{content:"\e231"}.glyphicon-ice-lolly-tasted:before{content:"\e232"}.glyphicon-education:before{content:"\e233"}.glyphicon-option-horizontal:before{content:"\e234"}.glyphicon-option-vertical:before{content:"\e235"}.glyphicon-menu-hamburger:before{content:"\e236"}.glyphicon-modal-window:before{content:"\e237"}.glyphicon-oil:before{content:"\e238"}.glyphicon-grain:before{content:"\e239"}.glyphicon-sunglasses:before{content:"\e240"}.glyphicon-text-size:before{content:"\e241"}.glyphicon-text-color:before{content:"\e242"}.glyphicon-text-background:before{content:"\e243"}.glyphicon-object-align-top:before{content:"\e244"}.glyphicon-object-align-bottom:before{content:"\e245"}.glyphicon-object-align-horizontal:before{content:"\e246"}.glyphicon-object-align-left:before{content:"\e247"}.glyphicon-object-align-vertical:before{content:"\e248"}.glyphicon-object-align-right:before{content:"\e249"}.glyphicon-triangle-right:before{content:"\e250"}.glyphicon-triangle-left:before{content:"\e251"}.glyphicon-triangle-bottom:before{content:"\e252"}.glyphicon-triangle-top:before{content:"\e253"}.glyphicon-console:before{content:"\e254"}.glyphicon-superscript:before{content:"\e255"}.glyphicon-subscript:before{content:"\e256"}.glyphicon-menu-left:before{content:"\e257"}.glyphicon-menu-right:before{content:"\e258"}.glyphicon-menu-down:before{content:"\e259"}.glyphicon-menu-up:before{content:"\e260"}*{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}:after,:before{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}html{font-size:10px;-webkit-tap-highlight-color:rgba(0,0,0,0)}body{font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;font-size:14px;line-height:1.42857143;color:#333;background-color:#fff}button,input,select,textarea{font-family:inherit;font-size:inherit;line-height:inherit}a{color:#337ab7;text-decoration:none}a:focus,a:hover{color:#23527c;text-decoration:underline}a:focus{outline:5px auto -webkit-focus-ring-color;outline-offset:-2px}figure{margin:0}img{vertical-align:middle}.carousel-inner>.item>a>img,.carousel-inner>.item>img,.img-responsive,.thumbnail a>img,.thumbnail>img{display:block;max-width:100%;height:auto}.img-rounded{border-radius:6px}.img-thumbnail{display:inline-block;max-width:100%;height:auto;padding:4px;line-height:1.42857143;background-color:#fff;border:1px solid #ddd;border-radius:4px;-webkit-transition:all .2s ease-in-out;-o-transition:all .2s ease-in-out;transition:all .2s ease-in-out}.img-circle{border-radius:50%}hr{margin-top:20px;margin-bottom:20px;border:0;border-top:1px solid #eee}.sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);border:0}.sr-only-focusable:active,.sr-only-focusable:focus{position:static;width:auto;height:auto;margin:0;overflow:visible;clip:auto}[role=button]{cursor:pointer}.h1,.h2,.h3,.h4,.h5,.h6,h1,h2,h3,h4,h5,h6{font-family:inherit;font-weight:500;line-height:1.1;color:inherit}.h1 .small,.h1 small,.h2 .small,.h2 small,.h3 .small,.h3 small,.h4 .small,.h4 small,.h5 .small,.h5 small,.h6 .small,.h6 small,h1 .small,h1 small,h2 .small,h2 small,h3 .small,h3 small,h4 .small,h4 small,h5 .small,h5 small,h6 .small,h6 small{font-weight:400;line-height:1;color:#777}.h1,.h2,.h3,h1,h2,h3{margin-top:20px;margin-bottom:10px}.h1 .small,.h1 small,.h2 .small,.h2 small,.h3 .small,.h3 small,h1 .small,h1 small,h2 .small,h2 small,h3 .small,h3 small{font-size:65%}.h4,.h5,.h6,h4,h5,h6{margin-top:10px;margin-bottom:10px}.h4 .small,.h4 small,.h5 .small,.h5 small,.h6 .small,.h6 small,h4 .small,h4 small,h5 .small,h5 small,h6 .small,h6 small{font-size:75%}.h1,h1{font-size:36px}.h2,h2{font-size:30px}.h3,h3{font-size:24px}.h4,h4{font-size:18px}.h5,h5{font-size:14px}.h6,h6{font-size:12px}p{margin:0 0 10px}.lead{margin-bottom:20px;font-size:16px;font-weight:300;line-height:1.4}@media (min-width:768px){.lead{font-size:21px}}.small,small{font-size:85%}.mark,mark{padding:.2em;background-color:#fcf8e3}.text-left{text-align:left}.text-right{text-align:right}.text-center{text-align:center}.text-justify{text-align:justify}.text-nowrap{white-space:nowrap}.text-lowercase{text-transform:lowercase}.text-uppercase{text-transform:uppercase}.text-capitalize{text-transform:capitalize}.text-muted{color:#777}.text-primary{color:#337ab7}a.text-primary:focus,a.text-primary:hover{color:#286090}.text-success{color:#3c763d}a.text-success:focus,a.text-success:hover{color:#2b542c}.text-info{color:#31708f}a.text-info:focus,a.text-info:hover{color:#245269}.text-warning{color:#8a6d3b}a.text-warning:focus,a.text-warning:hover{color:#66512c}.text-danger{color:#a94442}a.text-danger:focus,a.text-danger:hover{color:#843534}.bg-primary{color:#fff;background-color:#337ab7}a.bg-primary:focus,a.bg-primary:hover{background-color:#286090}.bg-success{background-color:#dff0d8}a.bg-success:focus,a.bg-success:hover{background-color:#c1e2b3}.bg-info{background-color:#d9edf7}a.bg-info:focus,a.bg-info:hover{background-color:#afd9ee}.bg-warning{background-color:#fcf8e3}a.bg-warning:focus,a.bg-warning:hover{background-color:#f7ecb5}.bg-danger{background-color:#f2dede}a.bg-danger:focus,a.bg-danger:hover{background-color:#e4b9b9}.page-header{padding-bottom:9px;margin:40px 0 20px;border-bottom:1px solid #eee}ol,ul{margin-top:0;margin-bottom:10px}ol ol,ol ul,ul ol,ul ul{margin-bottom:0}.list-unstyled{padding-left:0;list-style:none}.list-inline{padding-left:0;margin-left:-5px;list-style:none}.list-inline>li{display:inline-block;padding-right:5px;padding-left:5px}dl{margin-top:0;margin-bottom:20px}dd,dt{line-height:1.42857143}dt{font-weight:700}dd{margin-left:0}@media (min-width:768px){.dl-horizontal dt{float:left;width:160px;overflow:hidden;clear:left;text-align:right;text-overflow:ellipsis;white-space:nowrap}.dl-horizontal dd{margin-left:180px}}abbr[data-original-title],abbr[title]{cursor:help;border-bottom:1px dotted #777}.initialism{font-size:90%;text-transform:uppercase}blockquote{padding:10px 20px;margin:0 0 20px;font-size:17.5px;border-left:5px solid #eee}blockquote ol:last-child,blockquote p:last-child,blockquote ul:last-child{margin-bottom:0}blockquote .small,blockquote footer,blockquote small{display:block;font-size:80%;line-height:1.42857143;color:#777}blockquote .small:before,blockquote footer:before,blockquote small:before{content:'\2014 \00A0'}.blockquote-reverse,blockquote.pull-right{padding-right:15px;padding-left:0;text-align:right;border-right:5px solid #eee;border-left:0}.blockquote-reverse .small:before,.blockquote-reverse footer:before,.blockquote-reverse small:before,blockquote.pull-right .small:before,blockquote.pull-right footer:before,blockquote.pull-right small:before{content:''}.blockquote-reverse .small:after,.blockquote-reverse footer:after,.blockquote-reverse small:after,blockquote.pull-right .small:after,blockquote.pull-right footer:after,blockquote.pull-right small:after{content:'\00A0 \2014'}address{margin-bottom:20px;font-style:normal;line-height:1.42857143}code,kbd,pre,samp{font-family:Menlo,Monaco,Consolas,"Courier New",monospace}code{padding:2px 4px;font-size:90%;color:#c7254e;background-color:#f9f2f4;border-radius:4px}kbd{padding:2px 4px;font-size:90%;color:#fff;background-color:#333;border-radius:3px;-webkit-box-shadow:inset 0 -1px 0 rgba(0,0,0,.25);box-shadow:inset 0 -1px 0 rgba(0,0,0,.25)}kbd kbd{padding:0;font-size:100%;font-weight:700;-webkit-box-shadow:none;box-shadow:none}pre{display:block;padding:9.5px;margin:0 0 10px;font-size:13px;line-height:1.42857143;color:#333;word-break:break-all;word-wrap:break-word;background-color:#f5f5f5;border:1px solid #ccc;border-radius:4px}pre code{padding:0;font-size:inherit;color:inherit;white-space:pre-wrap;background-color:transparent;border-radius:0}.pre-scrollable{max-height:340px;overflow-y:scroll}.container{padding-right:15px;padding-left:15px;margin-right:auto;margin-left:auto}@media (min-width:768px){.container{width:750px}}@media (min-width:992px){.container{width:970px}}@media (min-width:1200px){.container{width:1170px}}.container-fluid{padding-right:15px;padding-left:15px;margin-right:auto;margin-left:auto}.row{margin-right:-15px;margin-left:-15px}.col-lg-1,.col-lg-10,.col-lg-11,.col-lg-12,.col-lg-2,.col-lg-3,.col-lg-4,.col-lg-5,.col-lg-6,.col-lg-7,.col-lg-8,.col-lg-9,.col-md-1,.col-md-10,.col-md-11,.col-md-12,.col-md-2,.col-md-3,.col-md-4,.col-md-5,.col-md-6,.col-md-7,.col-md-8,.col-md-9,.col-sm-1,.col-sm-10,.col-sm-11,.col-sm-12,.col-sm-2,.col-sm-3,.col-sm-4,.col-sm-5,.col-sm-6,.col-sm-7,.col-sm-8,.col-sm-9,.col-xs-1,.col-xs-10,.col-xs-11,.col-xs-12,.col-xs-2,.col-xs-3,.col-xs-4,.col-xs-5,.col-xs-6,.col-xs-7,.col-xs-8,.col-xs-9{position:relative;min-height:1px;padding-right:15px;padding-left:15px}.col-xs-1,.col-xs-10,.col-xs-11,.col-xs-12,.col-xs-2,.col-xs-3,.col-xs-4,.col-xs-5,.col-xs-6,.col-xs-7,.col-xs-8,.col-xs-9{float:left}.col-xs-12{width:100%}.col-xs-11{width:91.66666667%}.col-xs-10{width:83.33333333%}.col-xs-9{width:75%}.col-xs-8{width:66.66666667%}.col-xs-7{width:58.33333333%}.col-xs-6{width:50%}.col-xs-5{width:41.66666667%}.col-xs-4{width:33.33333333%}.col-xs-3{width:25%}.col-xs-2{width:16.66666667%}.col-xs-1{width:8.33333333%}.col-xs-pull-12{right:100%}.col-xs-pull-11{right:91.66666667%}.col-xs-pull-10{right:83.33333333%}.col-xs-pull-9{right:75%}.col-xs-pull-8{right:66.66666667%}.col-xs-pull-7{right:58.33333333%}.col-xs-pull-6{right:50%}.col-xs-pull-5{right:41.66666667%}.col-xs-pull-4{right:33.33333333%}.col-xs-pull-3{right:25%}.col-xs-pull-2{right:16.66666667%}.col-xs-pull-1{right:8.33333333%}.col-xs-pull-0{right:auto}.col-xs-push-12{left:100%}.col-xs-push-11{left:91.66666667%}.col-xs-push-10{left:83.33333333%}.col-xs-push-9{left:75%}.col-xs-push-8{left:66.66666667%}.col-xs-push-7{left:58.33333333%}.col-xs-push-6{left:50%}.col-xs-push-5{left:41.66666667%}.col-xs-push-4{left:33.33333333%}.col-xs-push-3{left:25%}.col-xs-push-2{left:16.66666667%}.col-xs-push-1{left:8.33333333%}.col-xs-push-0{left:auto}.col-xs-offset-12{margin-left:100%}.col-xs-offset-11{margin-left:91.66666667%}.col-xs-offset-10{margin-left:83.33333333%}.col-xs-offset-9{margin-left:75%}.col-xs-offset-8{margin-left:66.66666667%}.col-xs-offset-7{margin-left:58.33333333%}.col-xs-offset-6{margin-left:50%}.col-xs-offset-5{margin-left:41.66666667%}.col-xs-offset-4{margin-left:33.33333333%}.col-xs-offset-3{margin-left:25%}.col-xs-offset-2{margin-left:16.66666667%}.col-xs-offset-1{margin-left:8.33333333%}.col-xs-offset-0{margin-left:0}@media (min-width:768px){.col-sm-1,.col-sm-10,.col-sm-11,.col-sm-12,.col-sm-2,.col-sm-3,.col-sm-4,.col-sm-5,.col-sm-6,.col-sm-7,.col-sm-8,.col-sm-9{float:left}.col-sm-12{width:100%}.col-sm-11{width:91.66666667%}.col-sm-10{width:83.33333333%}.col-sm-9{width:75%}.col-sm-8{width:66.66666667%}.col-sm-7{width:58.33333333%}.col-sm-6{width:50%}.col-sm-5{width:41.66666667%}.col-sm-4{width:33.33333333%}.col-sm-3{width:25%}.col-sm-2{width:16.66666667%}.col-sm-1{width:8.33333333%}.col-sm-pull-12{right:100%}.col-sm-pull-11{right:91.66666667%}.col-sm-pull-10{right:83.33333333%}.col-sm-pull-9{right:75%}.col-sm-pull-8{right:66.66666667%}.col-sm-pull-7{right:58.33333333%}.col-sm-pull-6{right:50%}.col-sm-pull-5{right:41.66666667%}.col-sm-pull-4{right:33.33333333%}.col-sm-pull-3{right:25%}.col-sm-pull-2{right:16.66666667%}.col-sm-pull-1{right:8.33333333%}.col-sm-pull-0{right:auto}.col-sm-push-12{left:100%}.col-sm-push-11{left:91.66666667%}.col-sm-push-10{left:83.33333333%}.col-sm-push-9{left:75%}.col-sm-push-8{left:66.66666667%}.col-sm-push-7{left:58.33333333%}.col-sm-push-6{left:50%}.col-sm-push-5{left:41.66666667%}.col-sm-push-4{left:33.33333333%}.col-sm-push-3{left:25%}.col-sm-push-2{left:16.66666667%}.col-sm-push-1{left:8.33333333%}.col-sm-push-0{left:auto}.col-sm-offset-12{margin-left:100%}.col-sm-offset-11{margin-left:91.66666667%}.col-sm-offset-10{margin-left:83.33333333%}.col-sm-offset-9{margin-left:75%}.col-sm-offset-8{margin-left:66.66666667%}.col-sm-offset-7{margin-left:58.33333333%}.col-sm-offset-6{margin-left:50%}.col-sm-offset-5{margin-left:41.66666667%}.col-sm-offset-4{margin-left:33.33333333%}.col-sm-offset-3{margin-left:25%}.col-sm-offset-2{margin-left:16.66666667%}.col-sm-offset-1{margin-left:8.33333333%}.col-sm-offset-0{margin-left:0}}@media (min-width:992px){.col-md-1,.col-md-10,.col-md-11,.col-md-12,.col-md-2,.col-md-3,.col-md-4,.col-md-5,.col-md-6,.col-md-7,.col-md-8,.col-md-9{float:left}.col-md-12{width:100%}.col-md-11{width:91.66666667%}.col-md-10{width:83.33333333%}.col-md-9{width:75%}.col-md-8{width:66.66666667%}.col-md-7{width:58.33333333%}.col-md-6{width:50%}.col-md-5{width:41.66666667%}.col-md-4{width:33.33333333%}.col-md-3{width:25%}.col-md-2{width:16.66666667%}.col-md-1{width:8.33333333%}.col-md-pull-12{right:100%}.col-md-pull-11{right:91.66666667%}.col-md-pull-10{right:83.33333333%}.col-md-pull-9{right:75%}.col-md-pull-8{right:66.66666667%}.col-md-pull-7{right:58.33333333%}.col-md-pull-6{right:50%}.col-md-pull-5{right:41.66666667%}.col-md-pull-4{right:33.33333333%}.col-md-pull-3{right:25%}.col-md-pull-2{right:16.66666667%}.col-md-pull-1{right:8.33333333%}.col-md-pull-0{right:auto}.col-md-push-12{left:100%}.col-md-push-11{left:91.66666667%}.col-md-push-10{left:83.33333333%}.col-md-push-9{left:75%}.col-md-push-8{left:66.66666667%}.col-md-push-7{left:58.33333333%}.col-md-push-6{left:50%}.col-md-push-5{left:41.66666667%}.col-md-push-4{left:33.33333333%}.col-md-push-3{left:25%}.col-md-push-2{left:16.66666667%}.col-md-push-1{left:8.33333333%}.col-md-push-0{left:auto}.col-md-offset-12{margin-left:100%}.col-md-offset-11{margin-left:91.66666667%}.col-md-offset-10{margin-left:83.33333333%}.col-md-offset-9{margin-left:75%}.col-md-offset-8{margin-left:66.66666667%}.col-md-offset-7{margin-left:58.33333333%}.col-md-offset-6{margin-left:50%}.col-md-offset-5{margin-left:41.66666667%}.col-md-offset-4{margin-left:33.33333333%}.col-md-offset-3{margin-left:25%}.col-md-offset-2{margin-left:16.66666667%}.col-md-offset-1{margin-left:8.33333333%}.col-md-offset-0{margin-left:0}}@media (min-width:1200px){.col-lg-1,.col-lg-10,.col-lg-11,.col-lg-12,.col-lg-2,.col-lg-3,.col-lg-4,.col-lg-5,.col-lg-6,.col-lg-7,.col-lg-8,.col-lg-9{float:left}.col-lg-12{width:100%}.col-lg-11{width:91.66666667%}.col-lg-10{width:83.33333333%}.col-lg-9{width:75%}.col-lg-8{width:66.66666667%}.col-lg-7{width:58.33333333%}.col-lg-6{width:50%}.col-lg-5{width:41.66666667%}.col-lg-4{width:33.33333333%}.col-lg-3{width:25%}.col-lg-2{width:16.66666667%}.col-lg-1{width:8.33333333%}.col-lg-pull-12{right:100%}.col-lg-pull-11{right:91.66666667%}.col-lg-pull-10{right:83.33333333%}.col-lg-pull-9{right:75%}.col-lg-pull-8{right:66.66666667%}.col-lg-pull-7{right:58.33333333%}.col-lg-pull-6{right:50%}.col-lg-pull-5{right:41.66666667%}.col-lg-pull-4{right:33.33333333%}.col-lg-pull-3{right:25%}.col-lg-pull-2{right:16.66666667%}.col-lg-pull-1{right:8.33333333%}.col-lg-pull-0{right:auto}.col-lg-push-12{left:100%}.col-lg-push-11{left:91.66666667%}.col-lg-push-10{left:83.33333333%}.col-lg-push-9{left:75%}.col-lg-push-8{left:66.66666667%}.col-lg-push-7{left:58.33333333%}.col-lg-push-6{left:50%}.col-lg-push-5{left:41.66666667%}.col-lg-push-4{left:33.33333333%}.col-lg-push-3{left:25%}.col-lg-push-2{left:16.66666667%}.col-lg-push-1{left:8.33333333%}.col-lg-push-0{left:auto}.col-lg-offset-12{margin-left:100%}.col-lg-offset-11{margin-left:91.66666667%}.col-lg-offset-10{margin-left:83.33333333%}.col-lg-offset-9{margin-left:75%}.col-lg-offset-8{margin-left:66.66666667%}.col-lg-offset-7{margin-left:58.33333333%}.col-lg-offset-6{margin-left:50%}.col-lg-offset-5{margin-left:41.66666667%}.col-lg-offset-4{margin-left:33.33333333%}.col-lg-offset-3{margin-left:25%}.col-lg-offset-2{margin-left:16.66666667%}.col-lg-offset-1{margin-left:8.33333333%}.col-lg-offset-0{margin-left:0}}table{background-color:transparent}caption{padding-top:8px;padding-bottom:8px;color:#777;text-align:left}th{text-align:left}.table{width:100%;max-width:100%;margin-bottom:20px}.table>tbody>tr>td,.table>tbody>tr>th,.table>tfoot>tr>td,.table>tfoot>tr>th,.table>thead>tr>td,.table>thead>tr>th{padding:8px;line-height:1.42857143;vertical-align:top;border-top:1px solid #ddd}.table>thead>tr>th{vertical-align:bottom;border-bottom:2px solid #ddd}.table>caption+thead>tr:first-child>td,.table>caption+thead>tr:first-child>th,.table>colgroup+thead>tr:first-child>td,.table>colgroup+thead>tr:first-child>th,.table>thead:first-child>tr:first-child>td,.table>thead:first-child>tr:first-child>th{border-top:0}.table>tbody+tbody{border-top:2px solid #ddd}.table .table{background-color:#fff}.table-condensed>tbody>tr>td,.table-condensed>tbody>tr>th,.table-condensed>tfoot>tr>td,.table-condensed>tfoot>tr>th,.table-condensed>thead>tr>td,.table-condensed>thead>tr>th{padding:5px}.table-bordered{border:1px solid #ddd}.table-bordered>tbody>tr>td,.table-bordered>tbody>tr>th,.table-bordered>tfoot>tr>td,.table-bordered>tfoot>tr>th,.table-bordered>thead>tr>td,.table-bordered>thead>tr>th{border:1px solid #ddd}.table-bordered>thead>tr>td,.table-bordered>thead>tr>th{border-bottom-width:2px}.table-striped>tbody>tr:nth-of-type(odd){background-color:#f9f9f9}.table-hover>tbody>tr:hover{background-color:#f5f5f5}table col[class*=col-]{position:static;display:table-column;float:none}table td[class*=col-],table th[class*=col-]{position:static;display:table-cell;float:none}.table>tbody>tr.active>td,.table>tbody>tr.active>th,.table>tbody>tr>td.active,.table>tbody>tr>th.active,.table>tfoot>tr.active>td,.table>tfoot>tr.active>th,.table>tfoot>tr>td.active,.table>tfoot>tr>th.active,.table>thead>tr.active>td,.table>thead>tr.active>th,.table>thead>tr>td.active,.table>thead>tr>th.active{background-color:#f5f5f5}.table-hover>tbody>tr.active:hover>td,.table-hover>tbody>tr.active:hover>th,.table-hover>tbody>tr:hover>.active,.table-hover>tbody>tr>td.active:hover,.table-hover>tbody>tr>th.active:hover{background-color:#e8e8e8}.table>tbody>tr.success>td,.table>tbody>tr.success>th,.table>tbody>tr>td.success,.table>tbody>tr>th.success,.table>tfoot>tr.success>td,.table>tfoot>tr.success>th,.table>tfoot>tr>td.success,.table>tfoot>tr>th.success,.table>thead>tr.success>td,.table>thead>tr.success>th,.table>thead>tr>td.success,.table>thead>tr>th.success{background-color:#dff0d8}.table-hover>tbody>tr.success:hover>td,.table-hover>tbody>tr.success:hover>th,.table-hover>tbody>tr:hover>.success,.table-hover>tbody>tr>td.success:hover,.table-hover>tbody>tr>th.success:hover{background-color:#d0e9c6}.table>tbody>tr.info>td,.table>tbody>tr.info>th,.table>tbody>tr>td.info,.table>tbody>tr>th.info,.table>tfoot>tr.info>td,.table>tfoot>tr.info>th,.table>tfoot>tr>td.info,.table>tfoot>tr>th.info,.table>thead>tr.info>td,.table>thead>tr.info>th,.table>thead>tr>td.info,.table>thead>tr>th.info{background-color:#d9edf7}.table-hover>tbody>tr.info:hover>td,.table-hover>tbody>tr.info:hover>th,.table-hover>tbody>tr:hover>.info,.table-hover>tbody>tr>td.info:hover,.table-hover>tbody>tr>th.info:hover{background-color:#c4e3f3}.table>tbody>tr.warning>td,.table>tbody>tr.warning>th,.table>tbody>tr>td.warning,.table>tbody>tr>th.warning,.table>tfoot>tr.warning>td,.table>tfoot>tr.warning>th,.table>tfoot>tr>td.warning,.table>tfoot>tr>th.warning,.table>thead>tr.warning>td,.table>thead>tr.warning>th,.table>thead>tr>td.warning,.table>thead>tr>th.warning{background-color:#fcf8e3}.table-hover>tbody>tr.warning:hover>td,.table-hover>tbody>tr.warning:hover>th,.table-hover>tbody>tr:hover>.warning,.table-hover>tbody>tr>td.warning:hover,.table-hover>tbody>tr>th.warning:hover{background-color:#faf2cc}.table>tbody>tr.danger>td,.table>tbody>tr.danger>th,.table>tbody>tr>td.danger,.table>tbody>tr>th.danger,.table>tfoot>tr.danger>td,.table>tfoot>tr.danger>th,.table>tfoot>tr>td.danger,.table>tfoot>tr>th.danger,.table>thead>tr.danger>td,.table>thead>tr.danger>th,.table>thead>tr>td.danger,.table>thead>tr>th.danger{background-color:#f2dede}.table-hover>tbody>tr.danger:hover>td,.table-hover>tbody>tr.danger:hover>th,.table-hover>tbody>tr:hover>.danger,.table-hover>tbody>tr>td.danger:hover,.table-hover>tbody>tr>th.danger:hover{background-color:#ebcccc}.table-responsive{min-height:.01%;overflow-x:auto}@media screen and (max-width:767px){.table-responsive{width:100%;margin-bottom:15px;overflow-y:hidden;-ms-overflow-style:-ms-autohiding-scrollbar;border:1px solid #ddd}.table-responsive>.table{margin-bottom:0}.table-responsive>.table>tbody>tr>td,.table-responsive>.table>tbody>tr>th,.table-responsive>.table>tfoot>tr>td,.table-responsive>.table>tfoot>tr>th,.table-responsive>.table>thead>tr>td,.table-responsive>.table>thead>tr>th{white-space:nowrap}.table-responsive>.table-bordered{border:0}.table-responsive>.table-bordered>tbody>tr>td:first-child,.table-responsive>.table-bordered>tbody>tr>th:first-child,.table-responsive>.table-bordered>tfoot>tr>td:first-child,.table-responsive>.table-bordered>tfoot>tr>th:first-child,.table-responsive>.table-bordered>thead>tr>td:first-child,.table-responsive>.table-bordered>thead>tr>th:first-child{border-left:0}.table-responsive>.table-bordered>tbody>tr>td:last-child,.table-responsive>.table-bordered>tbody>tr>th:last-child,.table-responsive>.table-bordered>tfoot>tr>td:last-child,.table-responsive>.table-bordered>tfoot>tr>th:last-child,.table-responsive>.table-bordered>thead>tr>td:last-child,.table-responsive>.table-bordered>thead>tr>th:last-child{border-right:0}.table-responsive>.table-bordered>tbody>tr:last-child>td,.table-responsive>.table-bordered>tbody>tr:last-child>th,.table-responsive>.table-bordered>tfoot>tr:last-child>td,.table-responsive>.table-bordered>tfoot>tr:last-child>th{border-bottom:0}}fieldset{min-width:0;padding:0;margin:0;border:0}legend{display:block;width:100%;padding:0;margin-bottom:20px;font-size:21px;line-height:inherit;color:#333;border:0;border-bottom:1px solid #e5e5e5}label{display:inline-block;max-width:100%;margin-bottom:5px;font-weight:700}input[type=search]{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}input[type=checkbox],input[type=radio]{margin:4px 0 0;margin-top:1px\9;line-height:normal}input[type=file]{display:block}input[type=range]{display:block;width:100%}select[multiple],select[size]{height:auto}input[type=file]:focus,input[type=checkbox]:focus,input[type=radio]:focus{outline:5px auto -webkit-focus-ring-color;outline-offset:-2px}output{display:block;padding-top:7px;font-size:14px;line-height:1.42857143;color:#555}.form-control{display:block;width:100%;height:34px;padding:6px 12px;font-size:14px;line-height:1.42857143;color:#555;background-color:#fff;background-image:none;border:1px solid #ccc;border-radius:4px;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075);box-shadow:inset 0 1px 1px rgba(0,0,0,.075);-webkit-transition:border-color ease-in-out .15s,-webkit-box-shadow ease-in-out .15s;-o-transition:border-color ease-in-out .15s,box-shadow ease-in-out .15s;transition:border-color ease-in-out .15s,box-shadow ease-in-out .15s}.form-control:focus{border-color:#66afe9;outline:0;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 8px rgba(102,175,233,.6);box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 8px rgba(102,175,233,.6)}.form-control::-moz-placeholder{color:#999;opacity:1}.form-control:-ms-input-placeholder{color:#999}.form-control::-webkit-input-placeholder{color:#999}.form-control::-ms-expand{background-color:transparent;border:0}.form-control[disabled],.form-control[readonly],fieldset[disabled] .form-control{background-color:#eee;opacity:1}.form-control[disabled],fieldset[disabled] .form-control{cursor:not-allowed}textarea.form-control{height:auto}input[type=search]{-webkit-appearance:none}@media screen and (-webkit-min-device-pixel-ratio:0){input[type=date].form-control,input[type=time].form-control,input[type=datetime-local].form-control,input[type=month].form-control{line-height:34px}.input-group-sm input[type=date],.input-group-sm input[type=time],.input-group-sm input[type=datetime-local],.input-group-sm input[type=month],input[type=date].input-sm,input[type=time].input-sm,input[type=datetime-local].input-sm,input[type=month].input-sm{line-height:30px}.input-group-lg input[type=date],.input-group-lg input[type=time],.input-group-lg input[type=datetime-local],.input-group-lg input[type=month],input[type=date].input-lg,input[type=time].input-lg,input[type=datetime-local].input-lg,input[type=month].input-lg{line-height:46px}}.form-group{margin-bottom:15px}.checkbox,.radio{position:relative;display:block;margin-top:10px;margin-bottom:10px}.checkbox label,.radio label{min-height:20px;padding-left:20px;margin-bottom:0;font-weight:400;cursor:pointer}.checkbox input[type=checkbox],.checkbox-inline input[type=checkbox],.radio input[type=radio],.radio-inline input[type=radio]{position:absolute;margin-top:4px\9;margin-left:-20px}.checkbox+.checkbox,.radio+.radio{margin-top:-5px}.checkbox-inline,.radio-inline{position:relative;display:inline-block;padding-left:20px;margin-bottom:0;font-weight:400;vertical-align:middle;cursor:pointer}.checkbox-inline+.checkbox-inline,.radio-inline+.radio-inline{margin-top:0;margin-left:10px}fieldset[disabled] input[type=checkbox],fieldset[disabled] input[type=radio],input[type=checkbox].disabled,input[type=checkbox][disabled],input[type=radio].disabled,input[type=radio][disabled]{cursor:not-allowed}.checkbox-inline.disabled,.radio-inline.disabled,fieldset[disabled] .checkbox-inline,fieldset[disabled] .radio-inline{cursor:not-allowed}.checkbox.disabled label,.radio.disabled label,fieldset[disabled] .checkbox label,fieldset[disabled] .radio label{cursor:not-allowed}.form-control-static{min-height:34px;padding-top:7px;padding-bottom:7px;margin-bottom:0}.form-control-static.input-lg,.form-control-static.input-sm{padding-right:0;padding-left:0}.input-sm{height:30px;padding:5px 10px;font-size:12px;line-height:1.5;border-radius:3px}select.input-sm{height:30px;line-height:30px}select[multiple].input-sm,textarea.input-sm{height:auto}.form-group-sm .form-control{height:30px;padding:5px 10px;font-size:12px;line-height:1.5;border-radius:3px}.form-group-sm select.form-control{height:30px;line-height:30px}.form-group-sm select[multiple].form-control,.form-group-sm textarea.form-control{height:auto}.form-group-sm .form-control-static{height:30px;min-height:32px;padding:6px 10px;font-size:12px;line-height:1.5}.input-lg{height:46px;padding:10px 16px;font-size:18px;line-height:1.3333333;border-radius:6px}select.input-lg{height:46px;line-height:46px}select[multiple].input-lg,textarea.input-lg{height:auto}.form-group-lg .form-control{height:46px;padding:10px 16px;font-size:18px;line-height:1.3333333;border-radius:6px}.form-group-lg select.form-control{height:46px;line-height:46px}.form-group-lg select[multiple].form-control,.form-group-lg textarea.form-control{height:auto}.form-group-lg .form-control-static{height:46px;min-height:38px;padding:11px 16px;font-size:18px;line-height:1.3333333}.has-feedback{position:relative}.has-feedback .form-control{padding-right:42.5px}.form-control-feedback{position:absolute;top:0;right:0;z-index:2;display:block;width:34px;height:34px;line-height:34px;text-align:center;pointer-events:none}.form-group-lg .form-control+.form-control-feedback,.input-group-lg+.form-control-feedback,.input-lg+.form-control-feedback{width:46px;height:46px;line-height:46px}.form-group-sm .form-control+.form-control-feedback,.input-group-sm+.form-control-feedback,.input-sm+.form-control-feedback{width:30px;height:30px;line-height:30px}.has-success .checkbox,.has-success .checkbox-inline,.has-success .control-label,.has-success .help-block,.has-success .radio,.has-success .radio-inline,.has-success.checkbox label,.has-success.checkbox-inline label,.has-success.radio label,.has-success.radio-inline label{color:#3c763d}.has-success .form-control{border-color:#3c763d;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075);box-shadow:inset 0 1px 1px rgba(0,0,0,.075)}.has-success .form-control:focus{border-color:#2b542c;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #67b168;box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #67b168}.has-success .input-group-addon{color:#3c763d;background-color:#dff0d8;border-color:#3c763d}.has-success .form-control-feedback{color:#3c763d}.has-warning .checkbox,.has-warning .checkbox-inline,.has-warning .control-label,.has-warning .help-block,.has-warning .radio,.has-warning .radio-inline,.has-warning.checkbox label,.has-warning.checkbox-inline label,.has-warning.radio label,.has-warning.radio-inline label{color:#8a6d3b}.has-warning .form-control{border-color:#8a6d3b;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075);box-shadow:inset 0 1px 1px rgba(0,0,0,.075)}.has-warning .form-control:focus{border-color:#66512c;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #c0a16b;box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #c0a16b}.has-warning .input-group-addon{color:#8a6d3b;background-color:#fcf8e3;border-color:#8a6d3b}.has-warning .form-control-feedback{color:#8a6d3b}.has-error .checkbox,.has-error .checkbox-inline,.has-error .control-label,.has-error .help-block,.has-error .radio,.has-error .radio-inline,.has-error.checkbox label,.has-error.checkbox-inline label,.has-error.radio label,.has-error.radio-inline label{color:#a94442}.has-error .form-control{border-color:#a94442;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075);box-shadow:inset 0 1px 1px rgba(0,0,0,.075)}.has-error .form-control:focus{border-color:#843534;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #ce8483;box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #ce8483}.has-error .input-group-addon{color:#a94442;background-color:#f2dede;border-color:#a94442}.has-error .form-control-feedback{color:#a94442}.has-feedback label~.form-control-feedback{top:25px}.has-feedback label.sr-only~.form-control-feedback{top:0}.help-block{display:block;margin-top:5px;margin-bottom:10px;color:#737373}@media (min-width:768px){.form-inline .form-group{display:inline-block;margin-bottom:0;vertical-align:middle}.form-inline .form-control{display:inline-block;width:auto;vertical-align:middle}.form-inline .form-control-static{display:inline-block}.form-inline .input-group{display:inline-table;vertical-align:middle}.form-inline .input-group .form-control,.form-inline .input-group .input-group-addon,.form-inline .input-group .input-group-btn{width:auto}.form-inline .input-group>.form-control{width:100%}.form-inline .control-label{margin-bottom:0;vertical-align:middle}.form-inline .checkbox,.form-inline .radio{display:inline-block;margin-top:0;margin-bottom:0;vertical-align:middle}.form-inline .checkbox label,.form-inline .radio label{padding-left:0}.form-inline .checkbox input[type=checkbox],.form-inline .radio input[type=radio]{position:relative;margin-left:0}.form-inline .has-feedback .form-control-feedback{top:0}}.form-horizontal .checkbox,.form-horizontal .checkbox-inline,.form-horizontal .radio,.form-horizontal .radio-inline{padding-top:7px;margin-top:0;margin-bottom:0}.form-horizontal .checkbox,.form-horizontal .radio{min-height:27px}.form-horizontal .form-group{margin-right:-15px;margin-left:-15px}@media (min-width:768px){.form-horizontal .control-label{padding-top:7px;margin-bottom:0;text-align:right}}.form-horizontal .has-feedback .form-control-feedback{right:15px}@media (min-width:768px){.form-horizontal .form-group-lg .control-label{padding-top:11px;font-size:18px}}@media (min-width:768px){.form-horizontal .form-group-sm .control-label{padding-top:6px;font-size:12px}}.btn{display:inline-block;padding:6px 12px;margin-bottom:0;font-size:14px;font-weight:400;line-height:1.42857143;text-align:center;white-space:nowrap;vertical-align:middle;-ms-touch-action:manipulation;touch-action:manipulation;cursor:pointer;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;background-image:none;border:1px solid transparent;border-radius:4px}.btn.active.focus,.btn.active:focus,.btn.focus,.btn:active.focus,.btn:active:focus,.btn:focus{outline:5px auto -webkit-focus-ring-color;outline-offset:-2px}.btn.focus,.btn:focus,.btn:hover{color:#333;text-decoration:none}.btn.active,.btn:active{background-image:none;outline:0;-webkit-box-shadow:inset 0 3px 5px rgba(0,0,0,.125);box-shadow:inset 0 3px 5px rgba(0,0,0,.125)}.btn.disabled,.btn[disabled],fieldset[disabled] .btn{cursor:not-allowed;filter:alpha(opacity=65);-webkit-box-shadow:none;box-shadow:none;opacity:.65}a.btn.disabled,fieldset[disabled] a.btn{pointer-events:none}.btn-default{color:#333;background-color:#fff;border-color:#ccc}.btn-default.focus,.btn-default:focus{color:#333;background-color:#e6e6e6;border-color:#8c8c8c}.btn-default:hover{color:#333;background-color:#e6e6e6;border-color:#adadad}.btn-default.active,.btn-default:active,.open>.dropdown-toggle.btn-default{color:#333;background-color:#e6e6e6;border-color:#adadad}.btn-default.active.focus,.btn-default.active:focus,.btn-default.active:hover,.btn-default:active.focus,.btn-default:active:focus,.btn-default:active:hover,.open>.dropdown-toggle.btn-default.focus,.open>.dropdown-toggle.btn-default:focus,.open>.dropdown-toggle.btn-default:hover{color:#333;background-color:#d4d4d4;border-color:#8c8c8c}.btn-default.active,.btn-default:active,.open>.dropdown-toggle.btn-default{background-image:none}.btn-default.disabled.focus,.btn-default.disabled:focus,.btn-default.disabled:hover,.btn-default[disabled].focus,.btn-default[disabled]:focus,.btn-default[disabled]:hover,fieldset[disabled] .btn-default.focus,fieldset[disabled] .btn-default:focus,fieldset[disabled] .btn-default:hover{background-color:#fff;border-color:#ccc}.btn-default .badge{color:#fff;background-color:#333}.btn-primary{color:#fff;background-color:#337ab7;border-color:#2e6da4}.btn-primary.focus,.btn-primary:focus{color:#fff;background-color:#286090;border-color:#122b40}.btn-primary:hover{color:#fff;background-color:#286090;border-color:#204d74}.btn-primary.active,.btn-primary:active,.open>.dropdown-toggle.btn-primary{color:#fff;background-color:#286090;border-color:#204d74}.btn-primary.active.focus,.btn-primary.active:focus,.btn-primary.active:hover,.btn-primary:active.focus,.btn-primary:active:focus,.btn-primary:active:hover,.open>.dropdown-toggle.btn-primary.focus,.open>.dropdown-toggle.btn-primary:focus,.open>.dropdown-toggle.btn-primary:hover{color:#fff;background-color:#204d74;border-color:#122b40}.btn-primary.active,.btn-primary:active,.open>.dropdown-toggle.btn-primary{background-image:none}.btn-primary.disabled.focus,.btn-primary.disabled:focus,.btn-primary.disabled:hover,.btn-primary[disabled].focus,.btn-primary[disabled]:focus,.btn-primary[disabled]:hover,fieldset[disabled] .btn-primary.focus,fieldset[disabled] .btn-primary:focus,fieldset[disabled] .btn-primary:hover{background-color:#337ab7;border-color:#2e6da4}.btn-primary .badge{color:#337ab7;background-color:#fff}.btn-success{color:#fff;background-color:#5cb85c;border-color:#4cae4c}.btn-success.focus,.btn-success:focus{color:#fff;background-color:#449d44;border-color:#255625}.btn-success:hover{color:#fff;background-color:#449d44;border-color:#398439}.btn-success.active,.btn-success:active,.open>.dropdown-toggle.btn-success{color:#fff;background-color:#449d44;border-color:#398439}.btn-success.active.focus,.btn-success.active:focus,.btn-success.active:hover,.btn-success:active.focus,.btn-success:active:focus,.btn-success:active:hover,.open>.dropdown-toggle.btn-success.focus,.open>.dropdown-toggle.btn-success:focus,.open>.dropdown-toggle.btn-success:hover{color:#fff;background-color:#398439;border-color:#255625}.btn-success.active,.btn-success:active,.open>.dropdown-toggle.btn-success{background-image:none}.btn-success.disabled.focus,.btn-success.disabled:focus,.btn-success.disabled:hover,.btn-success[disabled].focus,.btn-success[disabled]:focus,.btn-success[disabled]:hover,fieldset[disabled] .btn-success.focus,fieldset[disabled] .btn-success:focus,fieldset[disabled] .btn-success:hover{background-color:#5cb85c;border-color:#4cae4c}.btn-success .badge{color:#5cb85c;background-color:#fff}.btn-info{color:#fff;background-color:#5bc0de;border-color:#46b8da}.btn-info.focus,.btn-info:focus{color:#fff;background-color:#31b0d5;border-color:#1b6d85}.btn-info:hover{color:#fff;background-color:#31b0d5;border-color:#269abc}.btn-info.active,.btn-info:active,.open>.dropdown-toggle.btn-info{color:#fff;background-color:#31b0d5;border-color:#269abc}.btn-info.active.focus,.btn-info.active:focus,.btn-info.active:hover,.btn-info:active.focus,.btn-info:active:focus,.btn-info:active:hover,.open>.dropdown-toggle.btn-info.focus,.open>.dropdown-toggle.btn-info:focus,.open>.dropdown-toggle.btn-info:hover{color:#fff;background-color:#269abc;border-color:#1b6d85}.btn-info.active,.btn-info:active,.open>.dropdown-toggle.btn-info{background-image:none}.btn-info.disabled.focus,.btn-info.disabled:focus,.btn-info.disabled:hover,.btn-info[disabled].focus,.btn-info[disabled]:focus,.btn-info[disabled]:hover,fieldset[disabled] .btn-info.focus,fieldset[disabled] .btn-info:focus,fieldset[disabled] .btn-info:hover{background-color:#5bc0de;border-color:#46b8da}.btn-info .badge{color:#5bc0de;background-color:#fff}.btn-warning{color:#fff;background-color:#f0ad4e;border-color:#eea236}.btn-warning.focus,.btn-warning:focus{color:#fff;background-color:#ec971f;border-color:#985f0d}.btn-warning:hover{color:#fff;background-color:#ec971f;border-color:#d58512}.btn-warning.active,.btn-warning:active,.open>.dropdown-toggle.btn-warning{color:#fff;background-color:#ec971f;border-color:#d58512}.btn-warning.active.focus,.btn-warning.active:focus,.btn-warning.active:hover,.btn-warning:active.focus,.btn-warning:active:focus,.btn-warning:active:hover,.open>.dropdown-toggle.btn-warning.focus,.open>.dropdown-toggle.btn-warning:focus,.open>.dropdown-toggle.btn-warning:hover{color:#fff;background-color:#d58512;border-color:#985f0d}.btn-warning.active,.btn-warning:active,.open>.dropdown-toggle.btn-warning{background-image:none}.btn-warning.disabled.focus,.btn-warning.disabled:focus,.btn-warning.disabled:hover,.btn-warning[disabled].focus,.btn-warning[disabled]:focus,.btn-warning[disabled]:hover,fieldset[disabled] .btn-warning.focus,fieldset[disabled] .btn-warning:focus,fieldset[disabled] .btn-warning:hover{background-color:#f0ad4e;border-color:#eea236}.btn-warning .badge{color:#f0ad4e;background-color:#fff}.btn-danger{color:#fff;background-color:#d9534f;border-color:#d43f3a}.btn-danger.focus,.btn-danger:focus{color:#fff;background-color:#c9302c;border-color:#761c19}.btn-danger:hover{color:#fff;background-color:#c9302c;border-color:#ac2925}.btn-danger.active,.btn-danger:active,.open>.dropdown-toggle.btn-danger{color:#fff;background-color:#c9302c;border-color:#ac2925}.btn-danger.active.focus,.btn-danger.active:focus,.btn-danger.active:hover,.btn-danger:active.focus,.btn-danger:active:focus,.btn-danger:active:hover,.open>.dropdown-toggle.btn-danger.focus,.open>.dropdown-toggle.btn-danger:focus,.open>.dropdown-toggle.btn-danger:hover{color:#fff;background-color:#ac2925;border-color:#761c19}.btn-danger.active,.btn-danger:active,.open>.dropdown-toggle.btn-danger{background-image:none}.btn-danger.disabled.focus,.btn-danger.disabled:focus,.btn-danger.disabled:hover,.btn-danger[disabled].focus,.btn-danger[disabled]:focus,.btn-danger[disabled]:hover,fieldset[disabled] .btn-danger.focus,fieldset[disabled] .btn-danger:focus,fieldset[disabled] .btn-danger:hover{background-color:#d9534f;border-color:#d43f3a}.btn-danger .badge{color:#d9534f;background-color:#fff}.btn-link{font-weight:400;color:#337ab7;border-radius:0}.btn-link,.btn-link.active,.btn-link:active,.btn-link[disabled],fieldset[disabled] .btn-link{background-color:transparent;-webkit-box-shadow:none;box-shadow:none}.btn-link,.btn-link:active,.btn-link:focus,.btn-link:hover{border-color:transparent}.btn-link:focus,.btn-link:hover{color:#23527c;text-decoration:underline;background-color:transparent}.btn-link[disabled]:focus,.btn-link[disabled]:hover,fieldset[disabled] .btn-link:focus,fieldset[disabled] .btn-link:hover{color:#777;text-decoration:none}.btn-group-lg>.btn,.btn-lg{padding:10px 16px;font-size:18px;line-height:1.3333333;border-radius:6px}.btn-group-sm>.btn,.btn-sm{padding:5px 10px;font-size:12px;line-height:1.5;border-radius:3px}.btn-group-xs>.btn,.btn-xs{padding:1px 5px;font-size:12px;line-height:1.5;border-radius:3px}.btn-block{display:block;width:100%}.btn-block+.btn-block{margin-top:5px}input[type=button].btn-block,input[type=reset].btn-block,input[type=submit].btn-block{width:100%}.fade{opacity:0;-webkit-transition:opacity .15s linear;-o-transition:opacity .15s linear;transition:opacity .15s linear}.fade.in{opacity:1}.collapse{display:none}.collapse.in{display:block}tr.collapse.in{display:table-row}tbody.collapse.in{display:table-row-group}.collapsing{position:relative;height:0;overflow:hidden;-webkit-transition-timing-function:ease;-o-transition-timing-function:ease;transition-timing-function:ease;-webkit-transition-duration:.35s;-o-transition-duration:.35s;transition-duration:.35s;-webkit-transition-property:height,visibility;-o-transition-property:height,visibility;transition-property:height,visibility}.caret{display:inline-block;width:0;height:0;margin-left:2px;vertical-align:middle;border-top:4px dashed;border-top:4px solid\9;border-right:4px solid transparent;border-left:4px solid transparent}.dropdown,.dropup{position:relative}.dropdown-toggle:focus{outline:0}.dropdown-menu{position:absolute;top:100%;left:0;z-index:1000;display:none;float:left;min-width:160px;padding:5px 0;margin:2px 0 0;font-size:14px;text-align:left;list-style:none;background-color:#fff;-webkit-background-clip:padding-box;background-clip:padding-box;border:1px solid #ccc;border:1px solid rgba(0,0,0,.15);border-radius:4px;-webkit-box-shadow:0 6px 12px rgba(0,0,0,.175);box-shadow:0 6px 12px rgba(0,0,0,.175)}.dropdown-menu.pull-right{right:0;left:auto}.dropdown-menu .divider{height:1px;margin:9px 0;overflow:hidden;background-color:#e5e5e5}.dropdown-menu>li>a{display:block;padding:3px 20px;clear:both;font-weight:400;line-height:1.42857143;color:#333;white-space:nowrap}.dropdown-menu>li>a:focus,.dropdown-menu>li>a:hover{color:#262626;text-decoration:none;background-color:#f5f5f5}.dropdown-menu>.active>a,.dropdown-menu>.active>a:focus,.dropdown-menu>.active>a:hover{color:#fff;text-decoration:none;background-color:#337ab7;outline:0}.dropdown-menu>.disabled>a,.dropdown-menu>.disabled>a:focus,.dropdown-menu>.disabled>a:hover{color:#777}.dropdown-menu>.disabled>a:focus,.dropdown-menu>.disabled>a:hover{text-decoration:none;cursor:not-allowed;background-color:transparent;background-image:none;filter:progid:DXImageTransform.Microsoft.gradient(enabled=false)}.open>.dropdown-menu{display:block}.open>a{outline:0}.dropdown-menu-right{right:0;left:auto}.dropdown-menu-left{right:auto;left:0}.dropdown-header{display:block;padding:3px 20px;font-size:12px;line-height:1.42857143;color:#777;white-space:nowrap}.dropdown-backdrop{position:fixed;top:0;right:0;bottom:0;left:0;z-index:990}.pull-right>.dropdown-menu{right:0;left:auto}.dropup .caret,.navbar-fixed-bottom .dropdown .caret{content:"";border-top:0;border-bottom:4px dashed;border-bottom:4px solid\9}.dropup .dropdown-menu,.navbar-fixed-bottom .dropdown .dropdown-menu{top:auto;bottom:100%;margin-bottom:2px}@media (min-width:768px){.navbar-right .dropdown-menu{right:0;left:auto}.navbar-right .dropdown-menu-left{right:auto;left:0}}.btn-group,.btn-group-vertical{position:relative;display:inline-block;vertical-align:middle}.btn-group-vertical>.btn,.btn-group>.btn{position:relative;float:left}.btn-group-vertical>.btn.active,.btn-group-vertical>.btn:active,.btn-group-vertical>.btn:focus,.btn-group-vertical>.btn:hover,.btn-group>.btn.active,.btn-group>.btn:active,.btn-group>.btn:focus,.btn-group>.btn:hover{z-index:2}.btn-group .btn+.btn,.btn-group .btn+.btn-group,.btn-group .btn-group+.btn,.btn-group .btn-group+.btn-group{margin-left:-1px}.btn-toolbar{margin-left:-5px}.btn-toolbar .btn,.btn-toolbar .btn-group,.btn-toolbar .input-group{float:left}.btn-toolbar>.btn,.btn-toolbar>.btn-group,.btn-toolbar>.input-group{margin-left:5px}.btn-group>.btn:not(:first-child):not(:last-child):not(.dropdown-toggle){border-radius:0}.btn-group>.btn:first-child{margin-left:0}.btn-group>.btn:first-child:not(:last-child):not(.dropdown-toggle){border-top-right-radius:0;border-bottom-right-radius:0}.btn-group>.btn:last-child:not(:first-child),.btn-group>.dropdown-toggle:not(:first-child){border-top-left-radius:0;border-bottom-left-radius:0}.btn-group>.btn-group{float:left}.btn-group>.btn-group:not(:first-child):not(:last-child)>.btn{border-radius:0}.btn-group>.btn-group:first-child:not(:last-child)>.btn:last-child,.btn-group>.btn-group:first-child:not(:last-child)>.dropdown-toggle{border-top-right-radius:0;border-bottom-right-radius:0}.btn-group>.btn-group:last-child:not(:first-child)>.btn:first-child{border-top-left-radius:0;border-bottom-left-radius:0}.btn-group .dropdown-toggle:active,.btn-group.open .dropdown-toggle{outline:0}.btn-group>.btn+.dropdown-toggle{padding-right:8px;padding-left:8px}.btn-group>.btn-lg+.dropdown-toggle{padding-right:12px;padding-left:12px}.btn-group.open .dropdown-toggle{-webkit-box-shadow:inset 0 3px 5px rgba(0,0,0,.125);box-shadow:inset 0 3px 5px rgba(0,0,0,.125)}.btn-group.open .dropdown-toggle.btn-link{-webkit-box-shadow:none;box-shadow:none}.btn .caret{margin-left:0}.btn-lg .caret{border-width:5px 5px 0;border-bottom-width:0}.dropup .btn-lg .caret{border-width:0 5px 5px}.btn-group-vertical>.btn,.btn-group-vertical>.btn-group,.btn-group-vertical>.btn-group>.btn{display:block;float:none;width:100%;max-width:100%}.btn-group-vertical>.btn-group>.btn{float:none}.btn-group-vertical>.btn+.btn,.btn-group-vertical>.btn+.btn-group,.btn-group-vertical>.btn-group+.btn,.btn-group-vertical>.btn-group+.btn-group{margin-top:-1px;margin-left:0}.btn-group-vertical>.btn:not(:first-child):not(:last-child){border-radius:0}.btn-group-vertical>.btn:first-child:not(:last-child){border-top-left-radius:4px;border-top-right-radius:4px;border-bottom-right-radius:0;border-bottom-left-radius:0}.btn-group-vertical>.btn:last-child:not(:first-child){border-top-left-radius:0;border-top-right-radius:0;border-bottom-right-radius:4px;border-bottom-left-radius:4px}.btn-group-vertical>.btn-group:not(:first-child):not(:last-child)>.btn{border-radius:0}.btn-group-vertical>.btn-group:first-child:not(:last-child)>.btn:last-child,.btn-group-vertical>.btn-group:first-child:not(:last-child)>.dropdown-toggle{border-bottom-right-radius:0;border-bottom-left-radius:0}.btn-group-vertical>.btn-group:last-child:not(:first-child)>.btn:first-child{border-top-left-radius:0;border-top-right-radius:0}.btn-group-justified{display:table;width:100%;table-layout:fixed;border-collapse:separate}.btn-group-justified>.btn,.btn-group-justified>.btn-group{display:table-cell;float:none;width:1%}.btn-group-justified>.btn-group .btn{width:100%}.btn-group-justified>.btn-group .dropdown-menu{left:auto}[data-toggle=buttons]>.btn input[type=checkbox],[data-toggle=buttons]>.btn input[type=radio],[data-toggle=buttons]>.btn-group>.btn input[type=checkbox],[data-toggle=buttons]>.btn-group>.btn input[type=radio]{position:absolute;clip:rect(0,0,0,0);pointer-events:none}.input-group{position:relative;display:table;border-collapse:separate}.input-group[class*=col-]{float:none;padding-right:0;padding-left:0}.input-group .form-control{position:relative;z-index:2;float:left;width:100%;margin-bottom:0}.input-group .form-control:focus{z-index:3}.input-group-lg>.form-control,.input-group-lg>.input-group-addon,.input-group-lg>.input-group-btn>.btn{height:46px;padding:10px 16px;font-size:18px;line-height:1.3333333;border-radius:6px}select.input-group-lg>.form-control,select.input-group-lg>.input-group-addon,select.input-group-lg>.input-group-btn>.btn{height:46px;line-height:46px}select[multiple].input-group-lg>.form-control,select[multiple].input-group-lg>.input-group-addon,select[multiple].input-group-lg>.input-group-btn>.btn,textarea.input-group-lg>.form-control,textarea.input-group-lg>.input-group-addon,textarea.input-group-lg>.input-group-btn>.btn{height:auto}.input-group-sm>.form-control,.input-group-sm>.input-group-addon,.input-group-sm>.input-group-btn>.btn{height:30px;padding:5px 10px;font-size:12px;line-height:1.5;border-radius:3px}select.input-group-sm>.form-control,select.input-group-sm>.input-group-addon,select.input-group-sm>.input-group-btn>.btn{height:30px;line-height:30px}select[multiple].input-group-sm>.form-control,select[multiple].input-group-sm>.input-group-addon,select[multiple].input-group-sm>.input-group-btn>.btn,textarea.input-group-sm>.form-control,textarea.input-group-sm>.input-group-addon,textarea.input-group-sm>.input-group-btn>.btn{height:auto}.input-group .form-control,.input-group-addon,.input-group-btn{display:table-cell}.input-group .form-control:not(:first-child):not(:last-child),.input-group-addon:not(:first-child):not(:last-child),.input-group-btn:not(:first-child):not(:last-child){border-radius:0}.input-group-addon,.input-group-btn{width:1%;white-space:nowrap;vertical-align:middle}.input-group-addon{padding:6px 12px;font-size:14px;font-weight:400;line-height:1;color:#555;text-align:center;background-color:#eee;border:1px solid #ccc;border-radius:4px}.input-group-addon.input-sm{padding:5px 10px;font-size:12px;border-radius:3px}.input-group-addon.input-lg{padding:10px 16px;font-size:18px;border-radius:6px}.input-group-addon input[type=checkbox],.input-group-addon input[type=radio]{margin-top:0}.input-group .form-control:first-child,.input-group-addon:first-child,.input-group-btn:first-child>.btn,.input-group-btn:first-child>.btn-group>.btn,.input-group-btn:first-child>.dropdown-toggle,.input-group-btn:last-child>.btn-group:not(:last-child)>.btn,.input-group-btn:last-child>.btn:not(:last-child):not(.dropdown-toggle){border-top-right-radius:0;border-bottom-right-radius:0}.input-group-addon:first-child{border-right:0}.input-group .form-control:last-child,.input-group-addon:last-child,.input-group-btn:first-child>.btn-group:not(:first-child)>.btn,.input-group-btn:first-child>.btn:not(:first-child),.input-group-btn:last-child>.btn,.input-group-btn:last-child>.btn-group>.btn,.input-group-btn:last-child>.dropdown-toggle{border-top-left-radius:0;border-bottom-left-radius:0}.input-group-addon:last-child{border-left:0}.input-group-btn{position:relative;font-size:0;white-space:nowrap}.input-group-btn>.btn{position:relative}.input-group-btn>.btn+.btn{margin-left:-1px}.input-group-btn>.btn:active,.input-group-btn>.btn:focus,.input-group-btn>.btn:hover{z-index:2}.input-group-btn:first-child>.btn,.input-group-btn:first-child>.btn-group{margin-right:-1px}.input-group-btn:last-child>.btn,.input-group-btn:last-child>.btn-group{z-index:2;margin-left:-1px}.nav{padding-left:0;margin-bottom:0;list-style:none}.nav>li{position:relative;display:block}.nav>li>a{position:relative;display:block;padding:10px 15px}.nav>li>a:focus,.nav>li>a:hover{text-decoration:none;background-color:#eee}.nav>li.disabled>a{color:#777}.nav>li.disabled>a:focus,.nav>li.disabled>a:hover{color:#777;text-decoration:none;cursor:not-allowed;background-color:transparent}.nav .open>a,.nav .open>a:focus,.nav .open>a:hover{background-color:#eee;border-color:#337ab7}.nav .nav-divider{height:1px;margin:9px 0;overflow:hidden;background-color:#e5e5e5}.nav>li>a>img{max-width:none}.nav-tabs{border-bottom:1px solid #ddd}.nav-tabs>li{float:left;margin-bottom:-1px}.nav-tabs>li>a{margin-right:2px;line-height:1.42857143;border:1px solid transparent;border-radius:4px 4px 0 0}.nav-tabs>li>a:hover{border-color:#eee #eee #ddd}.nav-tabs>li.active>a,.nav-tabs>li.active>a:focus,.nav-tabs>li.active>a:hover{color:#555;cursor:default;background-color:#fff;border:1px solid #ddd;border-bottom-color:transparent}.nav-tabs.nav-justified{width:100%;border-bottom:0}.nav-tabs.nav-justified>li{float:none}.nav-tabs.nav-justified>li>a{margin-bottom:5px;text-align:center}.nav-tabs.nav-justified>.dropdown .dropdown-menu{top:auto;left:auto}@media (min-width:768px){.nav-tabs.nav-justified>li{display:table-cell;width:1%}.nav-tabs.nav-justified>li>a{margin-bottom:0}}.nav-tabs.nav-justified>li>a{margin-right:0;border-radius:4px}.nav-tabs.nav-justified>.active>a,.nav-tabs.nav-justified>.active>a:focus,.nav-tabs.nav-justified>.active>a:hover{border:1px solid #ddd}@media (min-width:768px){.nav-tabs.nav-justified>li>a{border-bottom:1px solid #ddd;border-radius:4px 4px 0 0}.nav-tabs.nav-justified>.active>a,.nav-tabs.nav-justified>.active>a:focus,.nav-tabs.nav-justified>.active>a:hover{border-bottom-color:#fff}}.nav-pills>li{float:left}.nav-pills>li>a{border-radius:4px}.nav-pills>li+li{margin-left:2px}.nav-pills>li.active>a,.nav-pills>li.active>a:focus,.nav-pills>li.active>a:hover{color:#fff;background-color:#337ab7}.nav-stacked>li{float:none}.nav-stacked>li+li{margin-top:2px;margin-left:0}.nav-justified{width:100%}.nav-justified>li{float:none}.nav-justified>li>a{margin-bottom:5px;text-align:center}.nav-justified>.dropdown .dropdown-menu{top:auto;left:auto}@media (min-width:768px){.nav-justified>li{display:table-cell;width:1%}.nav-justified>li>a{margin-bottom:0}}.nav-tabs-justified{border-bottom:0}.nav-tabs-justified>li>a{margin-right:0;border-radius:4px}.nav-tabs-justified>.active>a,.nav-tabs-justified>.active>a:focus,.nav-tabs-justified>.active>a:hover{border:1px solid #ddd}@media (min-width:768px){.nav-tabs-justified>li>a{border-bottom:1px solid #ddd;border-radius:4px 4px 0 0}.nav-tabs-justified>.active>a,.nav-tabs-justified>.active>a:focus,.nav-tabs-justified>.active>a:hover{border-bottom-color:#fff}}.tab-content>.tab-pane{display:none}.tab-content>.active{display:block}.nav-tabs .dropdown-menu{margin-top:-1px;border-top-left-radius:0;border-top-right-radius:0}.navbar{position:relative;min-height:50px;margin-bottom:20px;border:1px solid transparent}@media (min-width:768px){.navbar{border-radius:4px}}@media (min-width:768px){.navbar-header{float:left}}.navbar-collapse{padding-right:15px;padding-left:15px;overflow-x:visible;-webkit-overflow-scrolling:touch;border-top:1px solid transparent;-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,.1);box-shadow:inset 0 1px 0 rgba(255,255,255,.1)}.navbar-collapse.in{overflow-y:auto}@media (min-width:768px){.navbar-collapse{width:auto;border-top:0;-webkit-box-shadow:none;box-shadow:none}.navbar-collapse.collapse{display:block!important;height:auto!important;padding-bottom:0;overflow:visible!important}.navbar-collapse.in{overflow-y:visible}.navbar-fixed-bottom .navbar-collapse,.navbar-fixed-top .navbar-collapse,.navbar-static-top .navbar-collapse{padding-right:0;padding-left:0}}.navbar-fixed-bottom .navbar-collapse,.navbar-fixed-top .navbar-collapse{max-height:340px}@media (max-device-width:480px) and (orientation:landscape){.navbar-fixed-bottom .navbar-collapse,.navbar-fixed-top .navbar-collapse{max-height:200px}}.container-fluid>.navbar-collapse,.container-fluid>.navbar-header,.container>.navbar-collapse,.container>.navbar-header{margin-right:-15px;margin-left:-15px}@media (min-width:768px){.container-fluid>.navbar-collapse,.container-fluid>.navbar-header,.container>.navbar-collapse,.container>.navbar-header{margin-right:0;margin-left:0}}.navbar-static-top{z-index:1000;border-width:0 0 1px}@media (min-width:768px){.navbar-static-top{border-radius:0}}.navbar-fixed-bottom,.navbar-fixed-top{position:fixed;right:0;left:0;z-index:1030}@media (min-width:768px){.navbar-fixed-bottom,.navbar-fixed-top{border-radius:0}}.navbar-fixed-top{top:0;border-width:0 0 1px}.navbar-fixed-bottom{bottom:0;margin-bottom:0;border-width:1px 0 0}.navbar-brand{float:left;height:50px;padding:15px 15px;font-size:18px;line-height:20px}.navbar-brand:focus,.navbar-brand:hover{text-decoration:none}.navbar-brand>img{display:block}@media (min-width:768px){.navbar>.container .navbar-brand,.navbar>.container-fluid .navbar-brand{margin-left:-15px}}.navbar-toggle{position:relative;float:right;padding:9px 10px;margin-top:8px;margin-right:15px;margin-bottom:8px;background-color:transparent;background-image:none;border:1px solid transparent;border-radius:4px}.navbar-toggle:focus{outline:0}.navbar-toggle .icon-bar{display:block;width:22px;height:2px;border-radius:1px}.navbar-toggle .icon-bar+.icon-bar{margin-top:4px}@media (min-width:768px){.navbar-toggle{display:none}}.navbar-nav{margin:7.5px -15px}.navbar-nav>li>a{padding-top:10px;padding-bottom:10px;line-height:20px}@media (max-width:767px){.navbar-nav .open .dropdown-menu{position:static;float:none;width:auto;margin-top:0;background-color:transparent;border:0;-webkit-box-shadow:none;box-shadow:none}.navbar-nav .open .dropdown-menu .dropdown-header,.navbar-nav .open .dropdown-menu>li>a{padding:5px 15px 5px 25px}.navbar-nav .open .dropdown-menu>li>a{line-height:20px}.navbar-nav .open .dropdown-menu>li>a:focus,.navbar-nav .open .dropdown-menu>li>a:hover{background-image:none}}@media (min-width:768px){.navbar-nav{float:left;margin:0}.navbar-nav>li{float:left}.navbar-nav>li>a{padding-top:15px;padding-bottom:15px}}.navbar-form{padding:10px 15px;margin-top:8px;margin-right:-15px;margin-bottom:8px;margin-left:-15px;border-top:1px solid transparent;border-bottom:1px solid transparent;-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,.1),0 1px 0 rgba(255,255,255,.1);box-shadow:inset 0 1px 0 rgba(255,255,255,.1),0 1px 0 rgba(255,255,255,.1)}@media (min-width:768px){.navbar-form .form-group{display:inline-block;margin-bottom:0;vertical-align:middle}.navbar-form .form-control{display:inline-block;width:auto;vertical-align:middle}.navbar-form .form-control-static{display:inline-block}.navbar-form .input-group{display:inline-table;vertical-align:middle}.navbar-form .input-group .form-control,.navbar-form .input-group .input-group-addon,.navbar-form .input-group .input-group-btn{width:auto}.navbar-form .input-group>.form-control{width:100%}.navbar-form .control-label{margin-bottom:0;vertical-align:middle}.navbar-form .checkbox,.navbar-form .radio{display:inline-block;margin-top:0;margin-bottom:0;vertical-align:middle}.navbar-form .checkbox label,.navbar-form .radio label{padding-left:0}.navbar-form .checkbox input[type=checkbox],.navbar-form .radio input[type=radio]{position:relative;margin-left:0}.navbar-form .has-feedback .form-control-feedback{top:0}}@media (max-width:767px){.navbar-form .form-group{margin-bottom:5px}.navbar-form .form-group:last-child{margin-bottom:0}}@media (min-width:768px){.navbar-form{width:auto;padding-top:0;padding-bottom:0;margin-right:0;margin-left:0;border:0;-webkit-box-shadow:none;box-shadow:none}}.navbar-nav>li>.dropdown-menu{margin-top:0;border-top-left-radius:0;border-top-right-radius:0}.navbar-fixed-bottom .navbar-nav>li>.dropdown-menu{margin-bottom:0;border-top-left-radius:4px;border-top-right-radius:4px;border-bottom-right-radius:0;border-bottom-left-radius:0}.navbar-btn{margin-top:8px;margin-bottom:8px}.navbar-btn.btn-sm{margin-top:10px;margin-bottom:10px}.navbar-btn.btn-xs{margin-top:14px;margin-bottom:14px}.navbar-text{margin-top:15px;margin-bottom:15px}@media (min-width:768px){.navbar-text{float:left;margin-right:15px;margin-left:15px}}@media (min-width:768px){.navbar-left{float:left!important}.navbar-right{float:right!important;margin-right:-15px}.navbar-right~.navbar-right{margin-right:0}}.navbar-default{background-color:#f8f8f8;border-color:#e7e7e7}.navbar-default .navbar-brand{color:#777}.navbar-default .navbar-brand:focus,.navbar-default .navbar-brand:hover{color:#5e5e5e;background-color:transparent}.navbar-default .navbar-text{color:#777}.navbar-default .navbar-nav>li>a{color:#777}.navbar-default .navbar-nav>li>a:focus,.navbar-default .navbar-nav>li>a:hover{color:#333;background-color:transparent}.navbar-default .navbar-nav>.active>a,.navbar-default .navbar-nav>.active>a:focus,.navbar-default .navbar-nav>.active>a:hover{color:#555;background-color:#e7e7e7}.navbar-default .navbar-nav>.disabled>a,.navbar-default .navbar-nav>.disabled>a:focus,.navbar-default .navbar-nav>.disabled>a:hover{color:#ccc;background-color:transparent}.navbar-default .navbar-toggle{border-color:#ddd}.navbar-default .navbar-toggle:focus,.navbar-default .navbar-toggle:hover{background-color:#ddd}.navbar-default .navbar-toggle .icon-bar{background-color:#888}.navbar-default .navbar-collapse,.navbar-default .navbar-form{border-color:#e7e7e7}.navbar-default .navbar-nav>.open>a,.navbar-default .navbar-nav>.open>a:focus,.navbar-default .navbar-nav>.open>a:hover{color:#555;background-color:#e7e7e7}@media (max-width:767px){.navbar-default .navbar-nav .open .dropdown-menu>li>a{color:#777}.navbar-default .navbar-nav .open .dropdown-menu>li>a:focus,.navbar-default .navbar-nav .open .dropdown-menu>li>a:hover{color:#333;background-color:transparent}.navbar-default .navbar-nav .open .dropdown-menu>.active>a,.navbar-default .navbar-nav .open .dropdown-menu>.active>a:focus,.navbar-default .navbar-nav .open .dropdown-menu>.active>a:hover{color:#555;background-color:#e7e7e7}.navbar-default .navbar-nav .open .dropdown-menu>.disabled>a,.navbar-default .navbar-nav .open .dropdown-menu>.disabled>a:focus,.navbar-default .navbar-nav .open .dropdown-menu>.disabled>a:hover{color:#ccc;background-color:transparent}}.navbar-default .navbar-link{color:#777}.navbar-default .navbar-link:hover{color:#333}.navbar-default .btn-link{color:#777}.navbar-default .btn-link:focus,.navbar-default .btn-link:hover{color:#333}.navbar-default .btn-link[disabled]:focus,.navbar-default .btn-link[disabled]:hover,fieldset[disabled] .navbar-default .btn-link:focus,fieldset[disabled] .navbar-default .btn-link:hover{color:#ccc}.navbar-inverse{background-color:#222;border-color:#080808}.navbar-inverse .navbar-brand{color:#9d9d9d}.navbar-inverse .navbar-brand:focus,.navbar-inverse .navbar-brand:hover{color:#fff;background-color:transparent}.navbar-inverse .navbar-text{color:#9d9d9d}.navbar-inverse .navbar-nav>li>a{color:#9d9d9d}.navbar-inverse .navbar-nav>li>a:focus,.navbar-inverse .navbar-nav>li>a:hover{color:#fff;background-color:transparent}.navbar-inverse .navbar-nav>.active>a,.navbar-inverse .navbar-nav>.active>a:focus,.navbar-inverse .navbar-nav>.active>a:hover{color:#fff;background-color:#080808}.navbar-inverse .navbar-nav>.disabled>a,.navbar-inverse .navbar-nav>.disabled>a:focus,.navbar-inverse .navbar-nav>.disabled>a:hover{color:#444;background-color:transparent}.navbar-inverse .navbar-toggle{border-color:#333}.navbar-inverse .navbar-toggle:focus,.navbar-inverse .navbar-toggle:hover{background-color:#333}.navbar-inverse .navbar-toggle .icon-bar{background-color:#fff}.navbar-inverse .navbar-collapse,.navbar-inverse .navbar-form{border-color:#101010}.navbar-inverse .navbar-nav>.open>a,.navbar-inverse .navbar-nav>.open>a:focus,.navbar-inverse .navbar-nav>.open>a:hover{color:#fff;background-color:#080808}@media (max-width:767px){.navbar-inverse .navbar-nav .open .dropdown-menu>.dropdown-header{border-color:#080808}.navbar-inverse .navbar-nav .open .dropdown-menu .divider{background-color:#080808}.navbar-inverse .navbar-nav .open .dropdown-menu>li>a{color:#9d9d9d}.navbar-inverse .navbar-nav .open .dropdown-menu>li>a:focus,.navbar-inverse .navbar-nav .open .dropdown-menu>li>a:hover{color:#fff;background-color:transparent}.navbar-inverse .navbar-nav .open .dropdown-menu>.active>a,.navbar-inverse .navbar-nav .open .dropdown-menu>.active>a:focus,.navbar-inverse .navbar-nav .open .dropdown-menu>.active>a:hover{color:#fff;background-color:#080808}.navbar-inverse .navbar-nav .open .dropdown-menu>.disabled>a,.navbar-inverse .navbar-nav .open .dropdown-menu>.disabled>a:focus,.navbar-inverse .navbar-nav .open .dropdown-menu>.disabled>a:hover{color:#444;background-color:transparent}}.navbar-inverse .navbar-link{color:#9d9d9d}.navbar-inverse .navbar-link:hover{color:#fff}.navbar-inverse .btn-link{color:#9d9d9d}.navbar-inverse .btn-link:focus,.navbar-inverse .btn-link:hover{color:#fff}.navbar-inverse .btn-link[disabled]:focus,.navbar-inverse .btn-link[disabled]:hover,fieldset[disabled] .navbar-inverse .btn-link:focus,fieldset[disabled] .navbar-inverse .btn-link:hover{color:#444}.breadcrumb{padding:8px 15px;margin-bottom:20px;list-style:none;background-color:#f5f5f5;border-radius:4px}.breadcrumb>li{display:inline-block}.breadcrumb>li+li:before{padding:0 5px;color:#ccc;content:"/\00a0"}.breadcrumb>.active{color:#777}.pagination{display:inline-block;padding-left:0;margin:20px 0;border-radius:4px}.pagination>li{display:inline}.pagination>li>a,.pagination>li>span{position:relative;float:left;padding:6px 12px;margin-left:-1px;line-height:1.42857143;color:#337ab7;text-decoration:none;background-color:#fff;border:1px solid #ddd}.pagination>li:first-child>a,.pagination>li:first-child>span{margin-left:0;border-top-left-radius:4px;border-bottom-left-radius:4px}.pagination>li:last-child>a,.pagination>li:last-child>span{border-top-right-radius:4px;border-bottom-right-radius:4px}.pagination>li>a:focus,.pagination>li>a:hover,.pagination>li>span:focus,.pagination>li>span:hover{z-index:2;color:#23527c;background-color:#eee;border-color:#ddd}.pagination>.active>a,.pagination>.active>a:focus,.pagination>.active>a:hover,.pagination>.active>span,.pagination>.active>span:focus,.pagination>.active>span:hover{z-index:3;color:#fff;cursor:default;background-color:#337ab7;border-color:#337ab7}.pagination>.disabled>a,.pagination>.disabled>a:focus,.pagination>.disabled>a:hover,.pagination>.disabled>span,.pagination>.disabled>span:focus,.pagination>.disabled>span:hover{color:#777;cursor:not-allowed;background-color:#fff;border-color:#ddd}.pagination-lg>li>a,.pagination-lg>li>span{padding:10px 16px;font-size:18px;line-height:1.3333333}.pagination-lg>li:first-child>a,.pagination-lg>li:first-child>span{border-top-left-radius:6px;border-bottom-left-radius:6px}.pagination-lg>li:last-child>a,.pagination-lg>li:last-child>span{border-top-right-radius:6px;border-bottom-right-radius:6px}.pagination-sm>li>a,.pagination-sm>li>span{padding:5px 10px;font-size:12px;line-height:1.5}.pagination-sm>li:first-child>a,.pagination-sm>li:first-child>span{border-top-left-radius:3px;border-bottom-left-radius:3px}.pagination-sm>li:last-child>a,.pagination-sm>li:last-child>span{border-top-right-radius:3px;border-bottom-right-radius:3px}.pager{padding-left:0;margin:20px 0;text-align:center;list-style:none}.pager li{display:inline}.pager li>a,.pager li>span{display:inline-block;padding:5px 14px;background-color:#fff;border:1px solid #ddd;border-radius:15px}.pager li>a:focus,.pager li>a:hover{text-decoration:none;background-color:#eee}.pager .next>a,.pager .next>span{float:right}.pager .previous>a,.pager .previous>span{float:left}.pager .disabled>a,.pager .disabled>a:focus,.pager .disabled>a:hover,.pager .disabled>span{color:#777;cursor:not-allowed;background-color:#fff}.label{display:inline;padding:.2em .6em .3em;font-size:75%;font-weight:700;line-height:1;color:#fff;text-align:center;white-space:nowrap;vertical-align:baseline;border-radius:.25em}a.label:focus,a.label:hover{color:#fff;text-decoration:none;cursor:pointer}.label:empty{display:none}.btn .label{position:relative;top:-1px}.label-default{background-color:#777}.label-default[href]:focus,.label-default[href]:hover{background-color:#5e5e5e}.label-primary{background-color:#337ab7}.label-primary[href]:focus,.label-primary[href]:hover{background-color:#286090}.label-success{background-color:#5cb85c}.label-success[href]:focus,.label-success[href]:hover{background-color:#449d44}.label-info{background-color:#5bc0de}.label-info[href]:focus,.label-info[href]:hover{background-color:#31b0d5}.label-warning{background-color:#f0ad4e}.label-warning[href]:focus,.label-warning[href]:hover{background-color:#ec971f}.label-danger{background-color:#d9534f}.label-danger[href]:focus,.label-danger[href]:hover{background-color:#c9302c}.badge{display:inline-block;min-width:10px;padding:3px 7px;font-size:12px;font-weight:700;line-height:1;color:#fff;text-align:center;white-space:nowrap;vertical-align:middle;background-color:#777;border-radius:10px}.badge:empty{display:none}.btn .badge{position:relative;top:-1px}.btn-group-xs>.btn .badge,.btn-xs .badge{top:0;padding:1px 5px}a.badge:focus,a.badge:hover{color:#fff;text-decoration:none;cursor:pointer}.list-group-item.active>.badge,.nav-pills>.active>a>.badge{color:#337ab7;background-color:#fff}.list-group-item>.badge{float:right}.list-group-item>.badge+.badge{margin-right:5px}.nav-pills>li>a>.badge{margin-left:3px}.jumbotron{padding-top:30px;padding-bottom:30px;margin-bottom:30px;color:inherit;background-color:#eee}.jumbotron .h1,.jumbotron h1{color:inherit}.jumbotron p{margin-bottom:15px;font-size:21px;font-weight:200}.jumbotron>hr{border-top-color:#d5d5d5}.container .jumbotron,.container-fluid .jumbotron{padding-right:15px;padding-left:15px;border-radius:6px}.jumbotron .container{max-width:100%}@media screen and (min-width:768px){.jumbotron{padding-top:48px;padding-bottom:48px}.container .jumbotron,.container-fluid .jumbotron{padding-right:60px;padding-left:60px}.jumbotron .h1,.jumbotron h1{font-size:63px}}.thumbnail{display:block;padding:4px;margin-bottom:20px;line-height:1.42857143;background-color:#fff;border:1px solid #ddd;border-radius:4px;-webkit-transition:border .2s ease-in-out;-o-transition:border .2s ease-in-out;transition:border .2s ease-in-out}.thumbnail a>img,.thumbnail>img{margin-right:auto;margin-left:auto}a.thumbnail.active,a.thumbnail:focus,a.thumbnail:hover{border-color:#337ab7}.thumbnail .caption{padding:9px;color:#333}.alert{padding:15px;margin-bottom:20px;border:1px solid transparent;border-radius:4px}.alert h4{margin-top:0;color:inherit}.alert .alert-link{font-weight:700}.alert>p,.alert>ul{margin-bottom:0}.alert>p+p{margin-top:5px}.alert-dismissable,.alert-dismissible{padding-right:35px}.alert-dismissable .close,.alert-dismissible .close{position:relative;top:-2px;right:-21px;color:inherit}.alert-success{color:#3c763d;background-color:#dff0d8;border-color:#d6e9c6}.alert-success hr{border-top-color:#c9e2b3}.alert-success .alert-link{color:#2b542c}.alert-info{color:#31708f;background-color:#d9edf7;border-color:#bce8f1}.alert-info hr{border-top-color:#a6e1ec}.alert-info .alert-link{color:#245269}.alert-warning{color:#8a6d3b;background-color:#fcf8e3;border-color:#faebcc}.alert-warning hr{border-top-color:#f7e1b5}.alert-warning .alert-link{color:#66512c}.alert-danger{color:#a94442;background-color:#f2dede;border-color:#ebccd1}.alert-danger hr{border-top-color:#e4b9c0}.alert-danger .alert-link{color:#843534}@-webkit-keyframes progress-bar-stripes{from{background-position:40px 0}to{background-position:0 0}}@-o-keyframes progress-bar-stripes{from{background-position:40px 0}to{background-position:0 0}}@keyframes progress-bar-stripes{from{background-position:40px 0}to{background-position:0 0}}.progress{height:20px;margin-bottom:20px;overflow:hidden;background-color:#f5f5f5;border-radius:4px;-webkit-box-shadow:inset 0 1px 2px rgba(0,0,0,.1);box-shadow:inset 0 1px 2px rgba(0,0,0,.1)}.progress-bar{float:left;width:0;height:100%;font-size:12px;line-height:20px;color:#fff;text-align:center;background-color:#337ab7;-webkit-box-shadow:inset 0 -1px 0 rgba(0,0,0,.15);box-shadow:inset 0 -1px 0 rgba(0,0,0,.15);-webkit-transition:width .6s ease;-o-transition:width .6s ease;transition:width .6s ease}.progress-bar-striped,.progress-striped .progress-bar{background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:-o-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);-webkit-background-size:40px 40px;background-size:40px 40px}.progress-bar.active,.progress.active .progress-bar{-webkit-animation:progress-bar-stripes 2s linear infinite;-o-animation:progress-bar-stripes 2s linear infinite;animation:progress-bar-stripes 2s linear infinite}.progress-bar-success{background-color:#5cb85c}.progress-striped .progress-bar-success{background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:-o-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent)}.progress-bar-info{background-color:#5bc0de}.progress-striped .progress-bar-info{background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:-o-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent)}.progress-bar-warning{background-color:#f0ad4e}.progress-striped .progress-bar-warning{background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:-o-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent)}.progress-bar-danger{background-color:#d9534f}.progress-striped .progress-bar-danger{background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:-o-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent)}.media{margin-top:15px}.media:first-child{margin-top:0}.media,.media-body{overflow:hidden;zoom:1}.media-body{width:10000px}.media-object{display:block}.media-object.img-thumbnail{max-width:none}.media-right,.media>.pull-right{padding-left:10px}.media-left,.media>.pull-left{padding-right:10px}.media-body,.media-left,.media-right{display:table-cell;vertical-align:top}.media-middle{vertical-align:middle}.media-bottom{vertical-align:bottom}.media-heading{margin-top:0;margin-bottom:5px}.media-list{padding-left:0;list-style:none}.list-group{padding-left:0;margin-bottom:20px}.list-group-item{position:relative;display:block;padding:10px 15px;margin-bottom:-1px;background-color:#fff;border:1px solid #ddd}.list-group-item:first-child{border-top-left-radius:4px;border-top-right-radius:4px}.list-group-item:last-child{margin-bottom:0;border-bottom-right-radius:4px;border-bottom-left-radius:4px}a.list-group-item,button.list-group-item{color:#555}a.list-group-item .list-group-item-heading,button.list-group-item .list-group-item-heading{color:#333}a.list-group-item:focus,a.list-group-item:hover,button.list-group-item:focus,button.list-group-item:hover{color:#555;text-decoration:none;background-color:#f5f5f5}button.list-group-item{width:100%;text-align:left}.list-group-item.disabled,.list-group-item.disabled:focus,.list-group-item.disabled:hover{color:#777;cursor:not-allowed;background-color:#eee}.list-group-item.disabled .list-group-item-heading,.list-group-item.disabled:focus .list-group-item-heading,.list-group-item.disabled:hover .list-group-item-heading{color:inherit}.list-group-item.disabled .list-group-item-text,.list-group-item.disabled:focus .list-group-item-text,.list-group-item.disabled:hover .list-group-item-text{color:#777}.list-group-item.active,.list-group-item.active:focus,.list-group-item.active:hover{z-index:2;color:#fff;background-color:#337ab7;border-color:#337ab7}.list-group-item.active .list-group-item-heading,.list-group-item.active .list-group-item-heading>.small,.list-group-item.active .list-group-item-heading>small,.list-group-item.active:focus .list-group-item-heading,.list-group-item.active:focus .list-group-item-heading>.small,.list-group-item.active:focus .list-group-item-heading>small,.list-group-item.active:hover .list-group-item-heading,.list-group-item.active:hover .list-group-item-heading>.small,.list-group-item.active:hover .list-group-item-heading>small{color:inherit}.list-group-item.active .list-group-item-text,.list-group-item.active:focus .list-group-item-text,.list-group-item.active:hover .list-group-item-text{color:#c7ddef}.list-group-item-success{color:#3c763d;background-color:#dff0d8}a.list-group-item-success,button.list-group-item-success{color:#3c763d}a.list-group-item-success .list-group-item-heading,button.list-group-item-success .list-group-item-heading{color:inherit}a.list-group-item-success:focus,a.list-group-item-success:hover,button.list-group-item-success:focus,button.list-group-item-success:hover{color:#3c763d;background-color:#d0e9c6}a.list-group-item-success.active,a.list-group-item-success.active:focus,a.list-group-item-success.active:hover,button.list-group-item-success.active,button.list-group-item-success.active:focus,button.list-group-item-success.active:hover{color:#fff;background-color:#3c763d;border-color:#3c763d}.list-group-item-info{color:#31708f;background-color:#d9edf7}a.list-group-item-info,button.list-group-item-info{color:#31708f}a.list-group-item-info .list-group-item-heading,button.list-group-item-info .list-group-item-heading{color:inherit}a.list-group-item-info:focus,a.list-group-item-info:hover,button.list-group-item-info:focus,button.list-group-item-info:hover{color:#31708f;background-color:#c4e3f3}a.list-group-item-info.active,a.list-group-item-info.active:focus,a.list-group-item-info.active:hover,button.list-group-item-info.active,button.list-group-item-info.active:focus,button.list-group-item-info.active:hover{color:#fff;background-color:#31708f;border-color:#31708f}.list-group-item-warning{color:#8a6d3b;background-color:#fcf8e3}a.list-group-item-warning,button.list-group-item-warning{color:#8a6d3b}a.list-group-item-warning .list-group-item-heading,button.list-group-item-warning .list-group-item-heading{color:inherit}a.list-group-item-warning:focus,a.list-group-item-warning:hover,button.list-group-item-warning:focus,button.list-group-item-warning:hover{color:#8a6d3b;background-color:#faf2cc}a.list-group-item-warning.active,a.list-group-item-warning.active:focus,a.list-group-item-warning.active:hover,button.list-group-item-warning.active,button.list-group-item-warning.active:focus,button.list-group-item-warning.active:hover{color:#fff;background-color:#8a6d3b;border-color:#8a6d3b}.list-group-item-danger{color:#a94442;background-color:#f2dede}a.list-group-item-danger,button.list-group-item-danger{color:#a94442}a.list-group-item-danger .list-group-item-heading,button.list-group-item-danger .list-group-item-heading{color:inherit}a.list-group-item-danger:focus,a.list-group-item-danger:hover,button.list-group-item-danger:focus,button.list-group-item-danger:hover{color:#a94442;background-color:#ebcccc}a.list-group-item-danger.active,a.list-group-item-danger.active:focus,a.list-group-item-danger.active:hover,button.list-group-item-danger.active,button.list-group-item-danger.active:focus,button.list-group-item-danger.active:hover{color:#fff;background-color:#a94442;border-color:#a94442}.list-group-item-heading{margin-top:0;margin-bottom:5px}.list-group-item-text{margin-bottom:0;line-height:1.3}.panel{margin-bottom:20px;background-color:#fff;border:1px solid transparent;border-radius:4px;-webkit-box-shadow:0 1px 1px rgba(0,0,0,.05);box-shadow:0 1px 1px rgba(0,0,0,.05)}.panel-body{padding:15px}.panel-heading{padding:10px 15px;border-bottom:1px solid transparent;border-top-left-radius:3px;border-top-right-radius:3px}.panel-heading>.dropdown .dropdown-toggle{color:inherit}.panel-title{margin-top:0;margin-bottom:0;font-size:16px;color:inherit}.panel-title>.small,.panel-title>.small>a,.panel-title>a,.panel-title>small,.panel-title>small>a{color:inherit}.panel-footer{padding:10px 15px;background-color:#f5f5f5;border-top:1px solid #ddd;border-bottom-right-radius:3px;border-bottom-left-radius:3px}.panel>.list-group,.panel>.panel-collapse>.list-group{margin-bottom:0}.panel>.list-group .list-group-item,.panel>.panel-collapse>.list-group .list-group-item{border-width:1px 0;border-radius:0}.panel>.list-group:first-child .list-group-item:first-child,.panel>.panel-collapse>.list-group:first-child .list-group-item:first-child{border-top:0;border-top-left-radius:3px;border-top-right-radius:3px}.panel>.list-group:last-child .list-group-item:last-child,.panel>.panel-collapse>.list-group:last-child .list-group-item:last-child{border-bottom:0;border-bottom-right-radius:3px;border-bottom-left-radius:3px}.panel>.panel-heading+.panel-collapse>.list-group .list-group-item:first-child{border-top-left-radius:0;border-top-right-radius:0}.panel-heading+.list-group .list-group-item:first-child{border-top-width:0}.list-group+.panel-footer{border-top-width:0}.panel>.panel-collapse>.table,.panel>.table,.panel>.table-responsive>.table{margin-bottom:0}.panel>.panel-collapse>.table caption,.panel>.table caption,.panel>.table-responsive>.table caption{padding-right:15px;padding-left:15px}.panel>.table-responsive:first-child>.table:first-child,.panel>.table:first-child{border-top-left-radius:3px;border-top-right-radius:3px}.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child,.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child,.panel>.table:first-child>tbody:first-child>tr:first-child,.panel>.table:first-child>thead:first-child>tr:first-child{border-top-left-radius:3px;border-top-right-radius:3px}.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child td:first-child,.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child th:first-child,.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child td:first-child,.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child th:first-child,.panel>.table:first-child>tbody:first-child>tr:first-child td:first-child,.panel>.table:first-child>tbody:first-child>tr:first-child th:first-child,.panel>.table:first-child>thead:first-child>tr:first-child td:first-child,.panel>.table:first-child>thead:first-child>tr:first-child th:first-child{border-top-left-radius:3px}.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child td:last-child,.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child th:last-child,.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child td:last-child,.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child th:last-child,.panel>.table:first-child>tbody:first-child>tr:first-child td:last-child,.panel>.table:first-child>tbody:first-child>tr:first-child th:last-child,.panel>.table:first-child>thead:first-child>tr:first-child td:last-child,.panel>.table:first-child>thead:first-child>tr:first-child th:last-child{border-top-right-radius:3px}.panel>.table-responsive:last-child>.table:last-child,.panel>.table:last-child{border-bottom-right-radius:3px;border-bottom-left-radius:3px}.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child,.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child,.panel>.table:last-child>tbody:last-child>tr:last-child,.panel>.table:last-child>tfoot:last-child>tr:last-child{border-bottom-right-radius:3px;border-bottom-left-radius:3px}.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child td:first-child,.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child th:first-child,.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child td:first-child,.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child th:first-child,.panel>.table:last-child>tbody:last-child>tr:last-child td:first-child,.panel>.table:last-child>tbody:last-child>tr:last-child th:first-child,.panel>.table:last-child>tfoot:last-child>tr:last-child td:first-child,.panel>.table:last-child>tfoot:last-child>tr:last-child th:first-child{border-bottom-left-radius:3px}.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child td:last-child,.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child th:last-child,.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child td:last-child,.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child th:last-child,.panel>.table:last-child>tbody:last-child>tr:last-child td:last-child,.panel>.table:last-child>tbody:last-child>tr:last-child th:last-child,.panel>.table:last-child>tfoot:last-child>tr:last-child td:last-child,.panel>.table:last-child>tfoot:last-child>tr:last-child th:last-child{border-bottom-right-radius:3px}.panel>.panel-body+.table,.panel>.panel-body+.table-responsive,.panel>.table+.panel-body,.panel>.table-responsive+.panel-body{border-top:1px solid #ddd}.panel>.table>tbody:first-child>tr:first-child td,.panel>.table>tbody:first-child>tr:first-child th{border-top:0}.panel>.table-bordered,.panel>.table-responsive>.table-bordered{border:0}.panel>.table-bordered>tbody>tr>td:first-child,.panel>.table-bordered>tbody>tr>th:first-child,.panel>.table-bordered>tfoot>tr>td:first-child,.panel>.table-bordered>tfoot>tr>th:first-child,.panel>.table-bordered>thead>tr>td:first-child,.panel>.table-bordered>thead>tr>th:first-child,.panel>.table-responsive>.table-bordered>tbody>tr>td:first-child,.panel>.table-responsive>.table-bordered>tbody>tr>th:first-child,.panel>.table-responsive>.table-bordered>tfoot>tr>td:first-child,.panel>.table-responsive>.table-bordered>tfoot>tr>th:first-child,.panel>.table-responsive>.table-bordered>thead>tr>td:first-child,.panel>.table-responsive>.table-bordered>thead>tr>th:first-child{border-left:0}.panel>.table-bordered>tbody>tr>td:last-child,.panel>.table-bordered>tbody>tr>th:last-child,.panel>.table-bordered>tfoot>tr>td:last-child,.panel>.table-bordered>tfoot>tr>th:last-child,.panel>.table-bordered>thead>tr>td:last-child,.panel>.table-bordered>thead>tr>th:last-child,.panel>.table-responsive>.table-bordered>tbody>tr>td:last-child,.panel>.table-responsive>.table-bordered>tbody>tr>th:last-child,.panel>.table-responsive>.table-bordered>tfoot>tr>td:last-child,.panel>.table-responsive>.table-bordered>tfoot>tr>th:last-child,.panel>.table-responsive>.table-bordered>thead>tr>td:last-child,.panel>.table-responsive>.table-bordered>thead>tr>th:last-child{border-right:0}.panel>.table-bordered>tbody>tr:first-child>td,.panel>.table-bordered>tbody>tr:first-child>th,.panel>.table-bordered>thead>tr:first-child>td,.panel>.table-bordered>thead>tr:first-child>th,.panel>.table-responsive>.table-bordered>tbody>tr:first-child>td,.panel>.table-responsive>.table-bordered>tbody>tr:first-child>th,.panel>.table-responsive>.table-bordered>thead>tr:first-child>td,.panel>.table-responsive>.table-bordered>thead>tr:first-child>th{border-bottom:0}.panel>.table-bordered>tbody>tr:last-child>td,.panel>.table-bordered>tbody>tr:last-child>th,.panel>.table-bordered>tfoot>tr:last-child>td,.panel>.table-bordered>tfoot>tr:last-child>th,.panel>.table-responsive>.table-bordered>tbody>tr:last-child>td,.panel>.table-responsive>.table-bordered>tbody>tr:last-child>th,.panel>.table-responsive>.table-bordered>tfoot>tr:last-child>td,.panel>.table-responsive>.table-bordered>tfoot>tr:last-child>th{border-bottom:0}.panel>.table-responsive{margin-bottom:0;border:0}.panel-group{margin-bottom:20px}.panel-group .panel{margin-bottom:0;border-radius:4px}.panel-group .panel+.panel{margin-top:5px}.panel-group .panel-heading{border-bottom:0}.panel-group .panel-heading+.panel-collapse>.list-group,.panel-group .panel-heading+.panel-collapse>.panel-body{border-top:1px solid #ddd}.panel-group .panel-footer{border-top:0}.panel-group .panel-footer+.panel-collapse .panel-body{border-bottom:1px solid #ddd}.panel-default{border-color:#ddd}.panel-default>.panel-heading{color:#333;background-color:#f5f5f5;border-color:#ddd}.panel-default>.panel-heading+.panel-collapse>.panel-body{border-top-color:#ddd}.panel-default>.panel-heading .badge{color:#f5f5f5;background-color:#333}.panel-default>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#ddd}.panel-primary{border-color:#337ab7}.panel-primary>.panel-heading{color:#fff;background-color:#337ab7;border-color:#337ab7}.panel-primary>.panel-heading+.panel-collapse>.panel-body{border-top-color:#337ab7}.panel-primary>.panel-heading .badge{color:#337ab7;background-color:#fff}.panel-primary>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#337ab7}.panel-success{border-color:#d6e9c6}.panel-success>.panel-heading{color:#3c763d;background-color:#dff0d8;border-color:#d6e9c6}.panel-success>.panel-heading+.panel-collapse>.panel-body{border-top-color:#d6e9c6}.panel-success>.panel-heading .badge{color:#dff0d8;background-color:#3c763d}.panel-success>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#d6e9c6}.panel-info{border-color:#bce8f1}.panel-info>.panel-heading{color:#31708f;background-color:#d9edf7;border-color:#bce8f1}.panel-info>.panel-heading+.panel-collapse>.panel-body{border-top-color:#bce8f1}.panel-info>.panel-heading .badge{color:#d9edf7;background-color:#31708f}.panel-info>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#bce8f1}.panel-warning{border-color:#faebcc}.panel-warning>.panel-heading{color:#8a6d3b;background-color:#fcf8e3;border-color:#faebcc}.panel-warning>.panel-heading+.panel-collapse>.panel-body{border-top-color:#faebcc}.panel-warning>.panel-heading .badge{color:#fcf8e3;background-color:#8a6d3b}.panel-warning>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#faebcc}.panel-danger{border-color:#ebccd1}.panel-danger>.panel-heading{color:#a94442;background-color:#f2dede;border-color:#ebccd1}.panel-danger>.panel-heading+.panel-collapse>.panel-body{border-top-color:#ebccd1}.panel-danger>.panel-heading .badge{color:#f2dede;background-color:#a94442}.panel-danger>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#ebccd1}.embed-responsive{position:relative;display:block;height:0;padding:0;overflow:hidden}.embed-responsive .embed-responsive-item,.embed-responsive embed,.embed-responsive iframe,.embed-responsive object,.embed-responsive video{position:absolute;top:0;bottom:0;left:0;width:100%;height:100%;border:0}.embed-responsive-16by9{padding-bottom:56.25%}.embed-responsive-4by3{padding-bottom:75%}.well{min-height:20px;padding:19px;margin-bottom:20px;background-color:#f5f5f5;border:1px solid #e3e3e3;border-radius:4px;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.05);box-shadow:inset 0 1px 1px rgba(0,0,0,.05)}.well blockquote{border-color:#ddd;border-color:rgba(0,0,0,.15)}.well-lg{padding:24px;border-radius:6px}.well-sm{padding:9px;border-radius:3px}.close{float:right;font-size:21px;font-weight:700;line-height:1;color:#000;text-shadow:0 1px 0 #fff;filter:alpha(opacity=20);opacity:.2}.close:focus,.close:hover{color:#000;text-decoration:none;cursor:pointer;filter:alpha(opacity=50);opacity:.5}button.close{-webkit-appearance:none;padding:0;cursor:pointer;background:0 0;border:0}.modal-open{overflow:hidden}.modal{position:fixed;top:0;right:0;bottom:0;left:0;z-index:1050;display:none;overflow:hidden;-webkit-overflow-scrolling:touch;outline:0}.modal.fade .modal-dialog{-webkit-transition:-webkit-transform .3s ease-out;-o-transition:-o-transform .3s ease-out;transition:transform .3s ease-out;-webkit-transform:translate(0,-25%);-ms-transform:translate(0,-25%);-o-transform:translate(0,-25%);transform:translate(0,-25%)}.modal.in .modal-dialog{-webkit-transform:translate(0,0);-ms-transform:translate(0,0);-o-transform:translate(0,0);transform:translate(0,0)}.modal-open .modal{overflow-x:hidden;overflow-y:auto}.modal-dialog{position:relative;width:auto;margin:10px}.modal-content{position:relative;background-color:#fff;-webkit-background-clip:padding-box;background-clip:padding-box;border:1px solid #999;border:1px solid rgba(0,0,0,.2);border-radius:6px;outline:0;-webkit-box-shadow:0 3px 9px rgba(0,0,0,.5);box-shadow:0 3px 9px rgba(0,0,0,.5)}.modal-backdrop{position:fixed;top:0;right:0;bottom:0;left:0;z-index:1040;background-color:#000}.modal-backdrop.fade{filter:alpha(opacity=0);opacity:0}.modal-backdrop.in{filter:alpha(opacity=50);opacity:.5}.modal-header{padding:15px;border-bottom:1px solid #e5e5e5}.modal-header .close{margin-top:-2px}.modal-title{margin:0;line-height:1.42857143}.modal-body{position:relative;padding:15px}.modal-footer{padding:15px;text-align:right;border-top:1px solid #e5e5e5}.modal-footer .btn+.btn{margin-bottom:0;margin-left:5px}.modal-footer .btn-group .btn+.btn{margin-left:-1px}.modal-footer .btn-block+.btn-block{margin-left:0}.modal-scrollbar-measure{position:absolute;top:-9999px;width:50px;height:50px;overflow:scroll}@media (min-width:768px){.modal-dialog{width:600px;margin:30px auto}.modal-content{-webkit-box-shadow:0 5px 15px rgba(0,0,0,.5);box-shadow:0 5px 15px rgba(0,0,0,.5)}.modal-sm{width:300px}}@media (min-width:992px){.modal-lg{width:900px}}.tooltip{position:absolute;z-index:1070;display:block;font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;font-size:12px;font-style:normal;font-weight:400;line-height:1.42857143;text-align:left;text-align:start;text-decoration:none;text-shadow:none;text-transform:none;letter-spacing:normal;word-break:normal;word-spacing:normal;word-wrap:normal;white-space:normal;filter:alpha(opacity=0);opacity:0;line-break:auto}.tooltip.in{filter:alpha(opacity=90);opacity:.9}.tooltip.top{padding:5px 0;margin-top:-3px}.tooltip.right{padding:0 5px;margin-left:3px}.tooltip.bottom{padding:5px 0;margin-top:3px}.tooltip.left{padding:0 5px;margin-left:-3px}.tooltip-inner{max-width:200px;padding:3px 8px;color:#fff;text-align:center;background-color:#000;border-radius:4px}.tooltip-arrow{position:absolute;width:0;height:0;border-color:transparent;border-style:solid}.tooltip.top .tooltip-arrow{bottom:0;left:50%;margin-left:-5px;border-width:5px 5px 0;border-top-color:#000}.tooltip.top-left .tooltip-arrow{right:5px;bottom:0;margin-bottom:-5px;border-width:5px 5px 0;border-top-color:#000}.tooltip.top-right .tooltip-arrow{bottom:0;left:5px;margin-bottom:-5px;border-width:5px 5px 0;border-top-color:#000}.tooltip.right .tooltip-arrow{top:50%;left:0;margin-top:-5px;border-width:5px 5px 5px 0;border-right-color:#000}.tooltip.left .tooltip-arrow{top:50%;right:0;margin-top:-5px;border-width:5px 0 5px 5px;border-left-color:#000}.tooltip.bottom .tooltip-arrow{top:0;left:50%;margin-left:-5px;border-width:0 5px 5px;border-bottom-color:#000}.tooltip.bottom-left .tooltip-arrow{top:0;right:5px;margin-top:-5px;border-width:0 5px 5px;border-bottom-color:#000}.tooltip.bottom-right .tooltip-arrow{top:0;left:5px;margin-top:-5px;border-width:0 5px 5px;border-bottom-color:#000}.popover{position:absolute;top:0;left:0;z-index:1060;display:none;max-width:276px;padding:1px;font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;font-size:14px;font-style:normal;font-weight:400;line-height:1.42857143;text-align:left;text-align:start;text-decoration:none;text-shadow:none;text-transform:none;letter-spacing:normal;word-break:normal;word-spacing:normal;word-wrap:normal;white-space:normal;background-color:#fff;-webkit-background-clip:padding-box;background-clip:padding-box;border:1px solid #ccc;border:1px solid rgba(0,0,0,.2);border-radius:6px;-webkit-box-shadow:0 5px 10px rgba(0,0,0,.2);box-shadow:0 5px 10px rgba(0,0,0,.2);line-break:auto}.popover.top{margin-top:-10px}.popover.right{margin-left:10px}.popover.bottom{margin-top:10px}.popover.left{margin-left:-10px}.popover-title{padding:8px 14px;margin:0;font-size:14px;background-color:#f7f7f7;border-bottom:1px solid #ebebeb;border-radius:5px 5px 0 0}.popover-content{padding:9px 14px}.popover>.arrow,.popover>.arrow:after{position:absolute;display:block;width:0;height:0;border-color:transparent;border-style:solid}.popover>.arrow{border-width:11px}.popover>.arrow:after{content:"";border-width:10px}.popover.top>.arrow{bottom:-11px;left:50%;margin-left:-11px;border-top-color:#999;border-top-color:rgba(0,0,0,.25);border-bottom-width:0}.popover.top>.arrow:after{bottom:1px;margin-left:-10px;content:" ";border-top-color:#fff;border-bottom-width:0}.popover.right>.arrow{top:50%;left:-11px;margin-top:-11px;border-right-color:#999;border-right-color:rgba(0,0,0,.25);border-left-width:0}.popover.right>.arrow:after{bottom:-10px;left:1px;content:" ";border-right-color:#fff;border-left-width:0}.popover.bottom>.arrow{top:-11px;left:50%;margin-left:-11px;border-top-width:0;border-bottom-color:#999;border-bottom-color:rgba(0,0,0,.25)}.popover.bottom>.arrow:after{top:1px;margin-left:-10px;content:" ";border-top-width:0;border-bottom-color:#fff}.popover.left>.arrow{top:50%;right:-11px;margin-top:-11px;border-right-width:0;border-left-color:#999;border-left-color:rgba(0,0,0,.25)}.popover.left>.arrow:after{right:1px;bottom:-10px;content:" ";border-right-width:0;border-left-color:#fff}.carousel{position:relative}.carousel-inner{position:relative;width:100%;overflow:hidden}.carousel-inner>.item{position:relative;display:none;-webkit-transition:.6s ease-in-out left;-o-transition:.6s ease-in-out left;transition:.6s ease-in-out left}.carousel-inner>.item>a>img,.carousel-inner>.item>img{line-height:1}@media all and (transform-3d),(-webkit-transform-3d){.carousel-inner>.item{-webkit-transition:-webkit-transform .6s ease-in-out;-o-transition:-o-transform .6s ease-in-out;transition:transform .6s ease-in-out;-webkit-backface-visibility:hidden;backface-visibility:hidden;-webkit-perspective:1000px;perspective:1000px}.carousel-inner>.item.active.right,.carousel-inner>.item.next{left:0;-webkit-transform:translate3d(100%,0,0);transform:translate3d(100%,0,0)}.carousel-inner>.item.active.left,.carousel-inner>.item.prev{left:0;-webkit-transform:translate3d(-100%,0,0);transform:translate3d(-100%,0,0)}.carousel-inner>.item.active,.carousel-inner>.item.next.left,.carousel-inner>.item.prev.right{left:0;-webkit-transform:translate3d(0,0,0);transform:translate3d(0,0,0)}}.carousel-inner>.active,.carousel-inner>.next,.carousel-inner>.prev{display:block}.carousel-inner>.active{left:0}.carousel-inner>.next,.carousel-inner>.prev{position:absolute;top:0;width:100%}.carousel-inner>.next{left:100%}.carousel-inner>.prev{left:-100%}.carousel-inner>.next.left,.carousel-inner>.prev.right{left:0}.carousel-inner>.active.left{left:-100%}.carousel-inner>.active.right{left:100%}.carousel-control{position:absolute;top:0;bottom:0;left:0;width:15%;font-size:20px;color:#fff;text-align:center;text-shadow:0 1px 2px rgba(0,0,0,.6);background-color:rgba(0,0,0,0);filter:alpha(opacity=50);opacity:.5}.carousel-control.left{background-image:-webkit-linear-gradient(left,rgba(0,0,0,.5) 0,rgba(0,0,0,.0001) 100%);background-image:-o-linear-gradient(left,rgba(0,0,0,.5) 0,rgba(0,0,0,.0001) 100%);background-image:-webkit-gradient(linear,left top,right top,from(rgba(0,0,0,.5)),to(rgba(0,0,0,.0001)));background-image:linear-gradient(to right,rgba(0,0,0,.5) 0,rgba(0,0,0,.0001) 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#80000000',endColorstr='#00000000',GradientType=1);background-repeat:repeat-x}.carousel-control.right{right:0;left:auto;background-image:-webkit-linear-gradient(left,rgba(0,0,0,.0001) 0,rgba(0,0,0,.5) 100%);background-image:-o-linear-gradient(left,rgba(0,0,0,.0001) 0,rgba(0,0,0,.5) 100%);background-image:-webkit-gradient(linear,left top,right top,from(rgba(0,0,0,.0001)),to(rgba(0,0,0,.5)));background-image:linear-gradient(to right,rgba(0,0,0,.0001) 0,rgba(0,0,0,.5) 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#00000000',endColorstr='#80000000',GradientType=1);background-repeat:repeat-x}.carousel-control:focus,.carousel-control:hover{color:#fff;text-decoration:none;filter:alpha(opacity=90);outline:0;opacity:.9}.carousel-control .glyphicon-chevron-left,.carousel-control .glyphicon-chevron-right,.carousel-control .icon-next,.carousel-control .icon-prev{position:absolute;top:50%;z-index:5;display:inline-block;margin-top:-10px}.carousel-control .glyphicon-chevron-left,.carousel-control .icon-prev{left:50%;margin-left:-10px}.carousel-control .glyphicon-chevron-right,.carousel-control .icon-next{right:50%;margin-right:-10px}.carousel-control .icon-next,.carousel-control .icon-prev{width:20px;height:20px;font-family:serif;line-height:1}.carousel-control .icon-prev:before{content:'\2039'}.carousel-control .icon-next:before{content:'\203a'}.carousel-indicators{position:absolute;bottom:10px;left:50%;z-index:15;width:60%;padding-left:0;margin-left:-30%;text-align:center;list-style:none}.carousel-indicators li{display:inline-block;width:10px;height:10px;margin:1px;text-indent:-999px;cursor:pointer;background-color:#000\9;background-color:rgba(0,0,0,0);border:1px solid #fff;border-radius:10px}.carousel-indicators .active{width:12px;height:12px;margin:0;background-color:#fff}.carousel-caption{position:absolute;right:15%;bottom:20px;left:15%;z-index:10;padding-top:20px;padding-bottom:20px;color:#fff;text-align:center;text-shadow:0 1px 2px rgba(0,0,0,.6)}.carousel-caption .btn{text-shadow:none}@media screen and (min-width:768px){.carousel-control .glyphicon-chevron-left,.carousel-control .glyphicon-chevron-right,.carousel-control .icon-next,.carousel-control .icon-prev{width:30px;height:30px;margin-top:-10px;font-size:30px}.carousel-control .glyphicon-chevron-left,.carousel-control .icon-prev{margin-left:-10px}.carousel-control .glyphicon-chevron-right,.carousel-control .icon-next{margin-right:-10px}.carousel-caption{right:20%;left:20%;padding-bottom:30px}.carousel-indicators{bottom:20px}}.btn-group-vertical>.btn-group:after,.btn-group-vertical>.btn-group:before,.btn-toolbar:after,.btn-toolbar:before,.clearfix:after,.clearfix:before,.container-fluid:after,.container-fluid:before,.container:after,.container:before,.dl-horizontal dd:after,.dl-horizontal dd:before,.form-horizontal .form-group:after,.form-horizontal .form-group:before,.modal-footer:after,.modal-footer:before,.modal-header:after,.modal-header:before,.nav:after,.nav:before,.navbar-collapse:after,.navbar-collapse:before,.navbar-header:after,.navbar-header:before,.navbar:after,.navbar:before,.pager:after,.pager:before,.panel-body:after,.panel-body:before,.row:after,.row:before{display:table;content:" "}.btn-group-vertical>.btn-group:after,.btn-toolbar:after,.clearfix:after,.container-fluid:after,.container:after,.dl-horizontal dd:after,.form-horizontal .form-group:after,.modal-footer:after,.modal-header:after,.nav:after,.navbar-collapse:after,.navbar-header:after,.navbar:after,.pager:after,.panel-body:after,.row:after{clear:both}.center-block{display:block;margin-right:auto;margin-left:auto}.pull-right{float:right!important}.pull-left{float:left!important}.hide{display:none!important}.show{display:block!important}.invisible{visibility:hidden}.text-hide{font:0/0 a;color:transparent;text-shadow:none;background-color:transparent;border:0}.hidden{display:none!important}.affix{position:fixed}@-ms-viewport{width:device-width}.visible-lg,.visible-md,.visible-sm,.visible-xs{display:none!important}.visible-lg-block,.visible-lg-inline,.visible-lg-inline-block,.visible-md-block,.visible-md-inline,.visible-md-inline-block,.visible-sm-block,.visible-sm-inline,.visible-sm-inline-block,.visible-xs-block,.visible-xs-inline,.visible-xs-inline-block{display:none!important}@media (max-width:767px){.visible-xs{display:block!important}table.visible-xs{display:table!important}tr.visible-xs{display:table-row!important}td.visible-xs,th.visible-xs{display:table-cell!important}}@media (max-width:767px){.visible-xs-block{display:block!important}}@media (max-width:767px){.visible-xs-inline{display:inline!important}}@media (max-width:767px){.visible-xs-inline-block{display:inline-block!important}}@media (min-width:768px) and (max-width:991px){.visible-sm{display:block!important}table.visible-sm{display:table!important}tr.visible-sm{display:table-row!important}td.visible-sm,th.visible-sm{display:table-cell!important}}@media (min-width:768px) and (max-width:991px){.visible-sm-block{display:block!important}}@media (min-width:768px) and (max-width:991px){.visible-sm-inline{display:inline!important}}@media (min-width:768px) and (max-width:991px){.visible-sm-inline-block{display:inline-block!important}}@media (min-width:992px) and (max-width:1199px){.visible-md{display:block!important}table.visible-md{display:table!important}tr.visible-md{display:table-row!important}td.visible-md,th.visible-md{display:table-cell!important}}@media (min-width:992px) and (max-width:1199px){.visible-md-block{display:block!important}}@media (min-width:992px) and (max-width:1199px){.visible-md-inline{display:inline!important}}@media (min-width:992px) and (max-width:1199px){.visible-md-inline-block{display:inline-block!important}}@media (min-width:1200px){.visible-lg{display:block!important}table.visible-lg{display:table!important}tr.visible-lg{display:table-row!important}td.visible-lg,th.visible-lg{display:table-cell!important}}@media (min-width:1200px){.visible-lg-block{display:block!important}}@media (min-width:1200px){.visible-lg-inline{display:inline!important}}@media (min-width:1200px){.visible-lg-inline-block{display:inline-block!important}}@media (max-width:767px){.hidden-xs{display:none!important}}@media (min-width:768px) and (max-width:991px){.hidden-sm{display:none!important}}@media (min-width:992px) and (max-width:1199px){.hidden-md{display:none!important}}@media (min-width:1200px){.hidden-lg{display:none!important}}.visible-print{display:none!important}@media print{.visible-print{display:block!important}table.visible-print{display:table!important}tr.visible-print{display:table-row!important}td.visible-print,th.visible-print{display:table-cell!important}}.visible-print-block{display:none!important}@media print{.visible-print-block{display:block!important}}.visible-print-inline{display:none!important}@media print{.visible-print-inline{display:inline!important}}.visible-print-inline-block{display:none!important}@media print{.visible-print-inline-block{display:inline-block!important}}@media print{.hidden-print{display:none!important}}.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('/static/blog/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}/*! + * IE10 viewport hack for Surface/desktop Windows 8 bug + * Copyright 2014-2015 Twitter, Inc. + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) + */@-ms-viewport{width:device-width}@-o-viewport{width:device-width}@viewport{width:device-width}/*! + * TODC Bootstrap v3.3.7-3.3.7 (http://todc.github.com/todc-bootstrap/) + * Copyright 2011-2016 Tim O'Donnell + * Licensed under the MIT (http://www.opensource.org/licenses/mit-license.php) license + */.panel-group .panel-heading a.collapsed:before,.panel-group .panel-heading a:before{position:relative;top:1px;display:inline-block;font-family:'Glyphicons Halflings';font-style:normal;font-weight:400;line-height:1;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.caret-left,.caret-right,.collapse-caret.collapsed:before,.collapse-caret:before,.dropdown-submenu>a:after{display:inline-block;width:0;height:0;margin-left:2px;vertical-align:middle;border-top:4px dashed;border-top:4px solid\9;border-right:4px solid transparent;border-left:4px solid transparent}body{font-family:Arial,Helvetica,sans-serif;font-size:13px;line-height:1.4;color:#333;background-color:#fff}button,input,select,textarea{font-family:inherit;font-size:inherit;line-height:inherit}a{color:#15c}a:focus,a:hover{color:#15c}.img-rounded{border-radius:1px}.img-thumbnail{display:inline-block;max-width:100%;height:auto;padding:0;line-height:1.4;background-color:#fff;border:3px solid #fff;border-radius:0;-webkit-box-shadow:0 0 0 1px #aaa;box-shadow:0 0 0 1px #aaa;-webkit-transition:none;-o-transition:none;transition:none}.caret-left,.caret-right,.collapse-caret.collapsed:before,.dropdown-submenu>a:after{vertical-align:baseline;border-top:4px solid transparent;border-right:0 dotted;border-bottom:4px solid transparent;border-left:4px solid}.caret-left{margin-right:2px;margin-left:0;border-right:4px solid;border-left:0 dotted}.scrollable-shadow{background:-webkit-gradient(linear,left top,left bottom,color-stop(30%,#fff),to(rgba(255,255,255,0))),-webkit-gradient(linear,left top,left bottom,from(rgba(255,255,255,0)),color-stop(70%,#fff)) 0 100%,radial-gradient(50% 0,farthest-side,rgba(0,0,0,.2),rgba(0,0,0,0)),radial-gradient(50% 100%,farthest-side,rgba(0,0,0,.2),rgba(0,0,0,0)) 0 100%;background:-webkit-linear-gradient(white 30%,rgba(255,255,255,0)),-webkit-linear-gradient(rgba(255,255,255,0),#fff 70%) 0 100%,-webkit-radial-gradient(50% 0,farthest-side,rgba(0,0,0,.2),rgba(0,0,0,0)),-webkit-radial-gradient(50% 100%,farthest-side,rgba(0,0,0,.2),rgba(0,0,0,0)) 0 100%;background:-o-linear-gradient(white 30%,rgba(255,255,255,0)),-o-linear-gradient(rgba(255,255,255,0),#fff 70%) 0 100%,-o-radial-gradient(50% 0,farthest-side,rgba(0,0,0,.2),rgba(0,0,0,0)),-o-radial-gradient(50% 100%,farthest-side,rgba(0,0,0,.2),rgba(0,0,0,0)) 0 100%;background:linear-gradient(white 30%,rgba(255,255,255,0)),linear-gradient(rgba(255,255,255,0),#fff 70%) 0 100%,radial-gradient(50% 0,farthest-side,rgba(0,0,0,.2),rgba(0,0,0,0)),radial-gradient(50% 100%,farthest-side,rgba(0,0,0,.2),rgba(0,0,0,0)) 0 100%;background:-webkit-gradient(linear,left top,left bottom,color-stop(30%,#fff),to(rgba(255,255,255,0))),-webkit-gradient(linear,left top,left bottom,from(rgba(255,255,255,0)),color-stop(70%,#fff)) 0 100%,radial-gradient(farthest-side at 50% 0,rgba(0,0,0,.2),rgba(0,0,0,0)),radial-gradient(farthest-side at 50% 100%,rgba(0,0,0,.2),rgba(0,0,0,0)) 0 100%;background:linear-gradient(white 30%,rgba(255,255,255,0)),linear-gradient(rgba(255,255,255,0),#fff 70%) 0 100%,radial-gradient(farthest-side at 50% 0,rgba(0,0,0,.2),rgba(0,0,0,0)),radial-gradient(farthest-side at 50% 100%,rgba(0,0,0,.2),rgba(0,0,0,0)) 0 100%;background-repeat:no-repeat;background-attachment:local,local,scroll,scroll;-webkit-background-size:100% 40px,100% 40px,100% 6px,100% 6px;background-size:100% 40px,100% 40px,100% 6px,100% 6px}.mark,mark{background-color:#f9edbe}.text-primary{color:#4d90fe}a.text-primary:focus,a.text-primary:hover{color:#1a70fe}.text-warning{color:#333}a.text-warning:focus,a.text-warning:hover{color:#1a1a1a}.bg-primary{color:#fff;background-color:#4d90fe}a.bg-primary:focus,a.bg-primary:hover{background-color:#1a70fe}.bg-warning{background-color:#f9edbe}a.bg-warning:focus,a.bg-warning:hover{background-color:#f5e08f}code{padding:2px 4px;border-radius:0}kbd{border-radius:1px}pre{padding:9px;margin:0 0 9px;font-size:12px;line-height:1.4;border-radius:0}table{background-color:transparent}caption{color:#999}.table{margin-bottom:18px}.table>tbody>tr>td,.table>tbody>tr>th,.table>tfoot>tr>td,.table>tfoot>tr>th,.table>thead>tr>td,.table>thead>tr>th{line-height:1.4;border-top:1px solid #ddd}.table>thead>tr>th{border-bottom:2px solid #ddd}.table>tbody+tbody{border-top:2px solid #ddd}.table .table{background-color:#fff}.table-bordered{border:1px solid #ddd}.table-bordered>tbody>tr>td,.table-bordered>tbody>tr>th,.table-bordered>tfoot>tr>td,.table-bordered>tfoot>tr>th,.table-bordered>thead>tr>td,.table-bordered>thead>tr>th{border:1px solid #ddd}.table-striped>tbody>tr:nth-of-type(odd){background-color:#f9f9f9}.table-hover>tbody>tr:hover{background-color:#ffc}.table>tbody>tr.warning>td,.table>tbody>tr.warning>th,.table>tbody>tr>td.warning,.table>tbody>tr>th.warning,.table>tfoot>tr.warning>td,.table>tfoot>tr.warning>th,.table>tfoot>tr>td.warning,.table>tfoot>tr>th.warning,.table>thead>tr.warning>td,.table>thead>tr.warning>th,.table>thead>tr>td.warning,.table>thead>tr>th.warning{background-color:#f9edbe}.table-hover>tbody>tr.warning:hover>td,.table-hover>tbody>tr.warning:hover>th,.table-hover>tbody>tr:hover>.warning,.table-hover>tbody>tr>td.warning:hover,.table-hover>tbody>tr>th.warning:hover{background-color:#f7e7a7}@media screen and (max-width:767px){.table-responsive{margin-bottom:13.5px;border:1px solid #ddd}}legend{margin-bottom:18px;font-size:19.5px}input[type=radio],input[type=checkbox]{margin:2px 0 0}output{padding-top:6px;font-size:13px;line-height:1.4;color:#555}.form-control{height:30px;-webkit-appearance:none;padding:5px 8px;font-size:13px;line-height:1.4;background-color:#fff;border:1px solid #d9d9d9;border-top-color:silver;border-radius:2px;-webkit-box-shadow:none;box-shadow:none;-webkit-transition:none;-o-transition:none;transition:none}.form-control:hover{border:1px solid #b9b9b9;border-top-color:#a0a0a0;-webkit-box-shadow:inset 0 1px 2px rgba(0,0,0,.1);box-shadow:inset 0 1px 2px rgba(0,0,0,.1)}.form-control:focus{border-color:#4d90fe;outline:0;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 8px rgba(77,144,254,.6);box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 8px rgba(77,144,254,.6)}.form-control:focus{-webkit-box-shadow:inset 0 1px 2px rgba(0,0,0,.3);box-shadow:inset 0 1px 2px rgba(0,0,0,.3)}.form-control::-ms-expand{background-color:transparent}.form-control[disabled],.form-control[readonly],fieldset[disabled] .form-control{background-color:#f1f1f1;border:1px solid #e5e5e5}.form-control[disabled]:active,.form-control[disabled]:focus,.form-control[disabled]:hover,.form-control[readonly]:active,.form-control[readonly]:focus,.form-control[readonly]:hover,fieldset[disabled] .form-control:active,fieldset[disabled] .form-control:focus,fieldset[disabled] .form-control:hover{border:1px solid #e5e5e5;-webkit-box-shadow:none;box-shadow:none}.form-control[readonly] .form-control{border:1px solid #d9d9d9}.form-control[readonly] .form-control:active,.form-control[readonly] .form-control:focus,.form-control[readonly] .form-control:hover{border:1px solid #d9d9d9}textarea.form-control{padding-right:4px}@media screen and (-webkit-min-device-pixel-ratio:0){input[type=date].form-control,input[type=time].form-control,input[type=datetime-local].form-control,input[type=month].form-control{line-height:30px}.input-group-sm input[type=date],.input-group-sm input[type=time],.input-group-sm input[type=datetime-local],.input-group-sm input[type=month],input[type=date].input-sm,input[type=time].input-sm,input[type=datetime-local].input-sm,input[type=month].input-sm{line-height:26px}.input-group-lg input[type=date],.input-group-lg input[type=time],.input-group-lg input[type=datetime-local],.input-group-lg input[type=month],input[type=date].input-lg,input[type=time].input-lg,input[type=datetime-local].input-lg,input[type=month].input-lg{line-height:38px}}.checkbox label,.radio label{min-height:18px}.checkbox input[type=checkbox],.checkbox-inline input[type=checkbox],.radio input[type=radio],.radio-inline input[type=radio],input[type=radio],input[type=checkbox]{position:relative;width:13px;width:16px\9;height:13px;height:16px\9;-webkit-appearance:none;background:#fff;border:1px solid #dcdcdc;border:1px solid transparent\9;border-radius:1px}.checkbox input[type=checkbox]:focus,.checkbox-inline input[type=checkbox]:focus,.radio input[type=radio]:focus,.radio-inline input[type=radio]:focus,input[type=radio]:focus,input[type=checkbox]:focus{border-color:#4d90fe;outline:0}.checkbox input[type=checkbox]:active,.checkbox-inline input[type=checkbox]:active,.radio input[type=radio]:active,.radio-inline input[type=radio]:active,input[type=radio]:active,input[type=checkbox]:active{background-color:#ebebeb;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffffff',endColorstr='#ffffffff',GradientType=0);border-color:#c6c6c6}.checkbox input[type=checkbox]:checked,.checkbox-inline input[type=checkbox]:checked,.radio input[type=radio]:checked,.radio-inline input[type=radio]:checked,input[type=radio]:checked,input[type=checkbox]:checked{background:#fff}.radio input[type=radio],.radio-inline input[type=radio],input[type=radio]{width:15px;width:18px\9;height:15px;height:18px\9;border-radius:1em}.radio input[type=radio]:checked::after,.radio-inline input[type=radio]:checked::after,input[type=radio]:checked::after{position:relative;top:3px;left:3px;display:block;width:7px;height:7px;content:'';background:#666;border-radius:1em}.checkbox input[type=checkbox]:hover,.checkbox-inline input[type=checkbox]:hover,input[type=checkbox]:hover{border-color:#c6c6c6;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.1);-webkit-box-shadow:none\9;box-shadow:inset 0 1px 1px rgba(0,0,0,.1);box-shadow:none\9}.checkbox input[type=checkbox]:checked::after,.checkbox-inline input[type=checkbox]:checked::after,input[type=checkbox]:checked::after{position:absolute;top:-6px;left:-5px;display:block;content:url(/static/assets/img/checkmark.png)}.form-control-static{min-height:31px;padding-top:6px;padding-bottom:6px}.input-sm{height:26px;padding:3px 8px;font-size:12px;line-height:1.5;border-radius:1px}select.input-sm{height:26px;line-height:26px}select[multiple].input-sm,textarea.input-sm{height:auto}.form-group-sm .form-control{height:26px;padding:3px 8px;font-size:12px;line-height:1.5;border-radius:1px}.form-group-sm select.form-control{height:26px;line-height:26px}.form-group-sm .form-control-static{height:26px;min-height:30px;padding:4px 8px;font-size:12px;line-height:1.5}.input-lg{height:38px;padding:9px 14px;font-size:14px;line-height:1.3;border-radius:1px}select.input-lg{height:38px;line-height:38px}select[multiple].input-lg,textarea.input-lg{height:auto}.form-group-lg .form-control{height:38px;padding:9px 14px;font-size:14px;line-height:1.3;border-radius:1px}.form-group-lg select.form-control{height:38px;line-height:38px}.form-group-lg .form-control-static{height:38px;min-height:32px;padding:10px 14px;font-size:14px;line-height:1.3}.has-feedback .form-control{padding-right:37.5px}.form-control-feedback{top:23px;width:30px;height:30px;line-height:30px}.form-group-lg .form-control+.form-control-feedback,.input-group-lg+.form-control-feedback,.input-lg+.form-control-feedback{width:38px;height:38px;line-height:38px}.form-group-sm .form-control+.form-control-feedback,.input-group-sm+.form-control-feedback,.input-sm+.form-control-feedback{width:26px;height:26px;line-height:26px}.has-success .checkbox,.has-success .checkbox-inline,.has-success .control-label,.has-success .help-block,.has-success .radio,.has-success .radio-inline,.has-success.checkbox label,.has-success.checkbox-inline label,.has-success.radio label,.has-success.radio-inline label{color:#3c763d}.has-success .form-control{border-color:#3c763d;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075);box-shadow:inset 0 1px 1px rgba(0,0,0,.075)}.has-success .form-control:focus{border-color:#2b542c;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #67b168;box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #67b168}.has-success .input-group-addon{color:#3c763d;background-color:#dff0d8;border-color:#3c763d}.has-success .form-control-feedback{color:#3c763d}.has-success .form-control{-webkit-box-shadow:none;box-shadow:none}.has-success .form-control:hover{border-color:#3c763d;-webkit-box-shadow:0 1px 2px rgba(0,0,0,.1) inset;box-shadow:0 1px 2px rgba(0,0,0,.1) inset}.has-success .form-control:focus{border-color:#3c763d;-webkit-box-shadow:0 1px 2px rgba(0,0,0,.3) inset;box-shadow:0 1px 2px rgba(0,0,0,.3) inset}.has-warning .checkbox,.has-warning .checkbox-inline,.has-warning .control-label,.has-warning .help-block,.has-warning .radio,.has-warning .radio-inline,.has-warning.checkbox label,.has-warning.checkbox-inline label,.has-warning.radio label,.has-warning.radio-inline label{color:#e09b17}.has-warning .form-control{border-color:#e09b17;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075);box-shadow:inset 0 1px 1px rgba(0,0,0,.075)}.has-warning .form-control:focus{border-color:#b27b12;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #f0c36d;box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #f0c36d}.has-warning .input-group-addon{color:#e09b17;background-color:#f9edbe;border-color:#e09b17}.has-warning .form-control-feedback{color:#e09b17}.has-warning .form-control{-webkit-box-shadow:none;box-shadow:none}.has-warning .form-control:hover{border-color:#e09b17;-webkit-box-shadow:0 1px 2px rgba(0,0,0,.1) inset;box-shadow:0 1px 2px rgba(0,0,0,.1) inset}.has-warning .form-control:focus{border-color:#e09b17;-webkit-box-shadow:0 1px 2px rgba(0,0,0,.3) inset;box-shadow:0 1px 2px rgba(0,0,0,.3) inset}.has-error .checkbox,.has-error .checkbox-inline,.has-error .control-label,.has-error .help-block,.has-error .radio,.has-error .radio-inline,.has-error.checkbox label,.has-error.checkbox-inline label,.has-error.radio label,.has-error.radio-inline label{color:#dd4b39}.has-error .form-control{border-color:#dd4b39;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075);box-shadow:inset 0 1px 1px rgba(0,0,0,.075)}.has-error .form-control:focus{border-color:#c23321;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #ec9a90;box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #ec9a90}.has-error .input-group-addon{color:#dd4b39;background-color:#f2dede;border-color:#dd4b39}.has-error .form-control-feedback{color:#dd4b39}.has-error .form-control{-webkit-box-shadow:none;box-shadow:none}.has-error .form-control:hover{border-color:#dd4b39;-webkit-box-shadow:0 1px 2px rgba(0,0,0,.1) inset;box-shadow:0 1px 2px rgba(0,0,0,.1) inset}.has-error .form-control:focus{border-color:#dd4b39;-webkit-box-shadow:0 1px 2px rgba(0,0,0,.3) inset;box-shadow:0 1px 2px rgba(0,0,0,.3) inset}.has-feedback label~.form-control-feedback{top:23px}.help-block{color:#777}.form-horizontal .checkbox-inline,.form-horizontal .control-label,.form-horizontal .radio-inline{padding-top:5px}@media (min-width:768px){.form-inline .form-group,.navbar-form .form-group{display:inline-block;margin-bottom:0;vertical-align:middle}.form-inline .form-control,.navbar-form .form-control{display:inline-block;width:auto;vertical-align:middle}.form-inline .form-control-static,.navbar-form .form-control-static{display:inline-block}.form-inline .input-group,.navbar-form .input-group{display:inline-table;vertical-align:middle}.form-inline .input-group .form-control,.form-inline .input-group .input-group-addon,.form-inline .input-group .input-group-btn,.navbar-form .input-group .form-control,.navbar-form .input-group .input-group-addon,.navbar-form .input-group .input-group-btn{width:auto}.form-inline .input-group>.form-control,.navbar-form .input-group>.form-control{width:100%}.form-inline .control-label,.navbar-form .control-label{margin-bottom:0;vertical-align:middle}.form-inline .checkbox,.form-inline .radio,.navbar-form .checkbox,.navbar-form .radio{display:inline-block;margin-top:0;margin-bottom:0;vertical-align:middle}.form-inline .checkbox label,.form-inline .radio label,.navbar-form .checkbox label,.navbar-form .radio label{padding-left:0}.form-inline .checkbox input[type=checkbox],.form-inline .radio input[type=radio],.navbar-form .checkbox input[type=checkbox],.navbar-form .radio input[type=radio]{position:relative;margin-bottom:-2px;margin-left:0}.form-inline .has-feedback .form-control-feedback,.navbar-form .has-feedback .form-control-feedback{top:0}}.form-horizontal .checkbox,.form-horizontal .checkbox-inline,.form-horizontal .radio,.form-horizontal .radio-inline{padding-top:6px}.form-horizontal .checkbox,.form-horizontal .radio{min-height:24px}@media (min-width:768px){.form-horizontal .control-label{padding-top:6px}.form-horizontal .has-feedback .form-control-feedback{top:0}}@media (min-width:768px){.form-horizontal .form-group-lg .control-label{padding-top:10px;font-size:14px}}@media (min-width:768px){.form-horizontal .form-group-sm .control-label{padding-top:4px;font-size:12px}}.btn{padding:5px 12px;font-size:13px;font-weight:700;line-height:18px;cursor:default;-webkit-background-clip:border-box;background-clip:border-box;border-radius:2px;-webkit-box-shadow:none;box-shadow:none}.btn:hover{-webkit-box-shadow:0 1px 1px rgba(0,0,0,.1);box-shadow:0 1px 1px rgba(0,0,0,.1)}.btn.active,.btn:active{-webkit-box-shadow:inset 0 1px 2px rgba(0,0,0,.1);box-shadow:inset 0 1px 2px rgba(0,0,0,.1)}.btn-default{color:#333;text-shadow:0 1px rgba(0,0,0,.1);text-shadow:0 1px 0 #fff;background-color:#f3f3f3;background-image:-webkit-linear-gradient(top,#f5f5f5 0,#f1f1f1 100%);background-image:-o-linear-gradient(top,#f5f5f5 0,#f1f1f1 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#f5f5f5),to(#f1f1f1));background-image:linear-gradient(to bottom,#f5f5f5 0,#f1f1f1 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff5f5f5',endColorstr='#fff1f1f1',GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);background-repeat:repeat-x;border:1px solid #dcdcdc}.btn-default:hover{text-shadow:0 1px rgba(0,0,0,.3);-webkit-box-shadow:0 1px 1px rgba(0,0,0,.2);box-shadow:0 1px 1px rgba(0,0,0,.2)}.btn-default.active,.btn-default.focus,.btn-default:active,.btn-default:focus,.btn-default:hover,.open>.dropdown-toggle.btn-default{color:#333;background-color:#e4e4e4;background-image:-webkit-linear-gradient(top,#f5f5f5 0,#e4e4e4 100%);background-image:-o-linear-gradient(top,#f5f5f5 0,#e4e4e4 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#f5f5f5),to(#e4e4e4));background-image:linear-gradient(to bottom,#f5f5f5 0,#e4e4e4 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff5f5f5',endColorstr='#ffe4e4e4',GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);background-repeat:repeat-x;border:1px solid #cfcfcf}.btn-default.active,.btn-default:active,.open>.dropdown-toggle.btn-default{text-shadow:0 1px rgba(0,0,0,.3);background-image:-webkit-linear-gradient(top,#f5f5f5 0,#d8d8d8 100%);background-image:-o-linear-gradient(top,#f5f5f5 0,#d8d8d8 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#f5f5f5),to(#d8d8d8));background-image:linear-gradient(to bottom,#f5f5f5 0,#d8d8d8 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff5f5f5',endColorstr='#ffd8d8d8',GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);background-repeat:repeat-x;border:1px solid #c3c3c3;-webkit-box-shadow:inset 0 1px 2px rgba(0,0,0,.3);box-shadow:inset 0 1px 2px rgba(0,0,0,.3)}.btn-default.focus,.btn-default:focus{border:1px solid #dcdcdc;-webkit-box-shadow:inset 0 0 0 1px #fff;box-shadow:inset 0 0 0 1px #fff}.btn-default.disabled.focus,.btn-default.disabled:focus,.btn-default.disabled:hover,.btn-default[disabled].focus,.btn-default[disabled]:focus,.btn-default[disabled]:hover,fieldset[disabled] .btn-default.focus,fieldset[disabled] .btn-default:focus,fieldset[disabled] .btn-default:hover{background-color:#f5f5f5;background-image:-webkit-linear-gradient(top,#f5f5f5 0,#f1f1f1 100%);background-image:-o-linear-gradient(top,#f5f5f5 0,#f1f1f1 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#f5f5f5),to(#f1f1f1));background-image:linear-gradient(to bottom,#f5f5f5 0,#f1f1f1 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff5f5f5',endColorstr='#fff1f1f1',GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);background-repeat:repeat-x;border:1px solid #dcdcdc;-webkit-box-shadow:none;box-shadow:none}.btn-default .badge{color:#dcdcdc;background-color:#333}.btn-default:hover{text-shadow:none;background-image:-webkit-linear-gradient(top,#f8f8f8 0,#f1f1f1 100%);background-image:-o-linear-gradient(top,#f8f8f8 0,#f1f1f1 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#f8f8f8),to(#f1f1f1));background-image:linear-gradient(to bottom,#f8f8f8 0,#f1f1f1 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff8f8f8',endColorstr='#fff1f1f1',GradientType=0);background-repeat:repeat-x;background-position:0 0;border-color:#c6c6c6;-webkit-box-shadow:0 1px 1px rgba(0,0,0,.1);box-shadow:0 1px 1px rgba(0,0,0,.1);-webkit-transition:none;-o-transition:none;transition:none}.btn-default.active,.btn-default:active,.open .dropdown-toggle.btn-default{text-shadow:0 1px 0 #fff;background-color:#e8e8e8;background-image:-webkit-linear-gradient(top,#f6f6f6 0,#f1f1f1 100%);background-image:-o-linear-gradient(top,#f6f6f6 0,#f1f1f1 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#f6f6f6),to(#f1f1f1));background-image:linear-gradient(to bottom,#f6f6f6 0,#f1f1f1 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff6f6f6',endColorstr='#fff1f1f1',GradientType=0);background-repeat:repeat-x;border:1px solid #dcdcdc;-webkit-box-shadow:inset 0 1px 2px rgba(0,0,0,.1);box-shadow:inset 0 1px 2px rgba(0,0,0,.1)}.btn-default.focus,.btn-default:focus{background-color:#f3f3f3;border-color:#4d90fe;outline-style:none}.btn-default.disabled.focus,.btn-default.disabled:active,.btn-default.disabled:focus,.btn-default.disabled:hover,.btn-default[disabled].focus,.btn-default[disabled]:active,.btn-default[disabled]:focus,.btn-default[disabled]:hover,fieldset[disabled] .btn-default.focus,fieldset[disabled] .btn-default:active,fieldset[disabled] .btn-default:focus,fieldset[disabled] .btn-default:hover{text-shadow:none;background-color:#f3f3f3}.btn-default .badge{color:#f3f3f3;text-shadow:none}.btn-primary{color:#fff;text-shadow:0 1px rgba(0,0,0,.1);background-image:-webkit-linear-gradient(top,#4d90fe 0,#4787ed 100%);background-image:-o-linear-gradient(top,#4d90fe 0,#4787ed 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#4d90fe),to(#4787ed));background-image:linear-gradient(to bottom,#4d90fe 0,#4787ed 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff4d90fe',endColorstr='#ff4787ed',GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);background-repeat:repeat-x;border:1px solid #3079ed}.btn-primary:hover{text-shadow:0 1px rgba(0,0,0,.3);-webkit-box-shadow:0 1px 1px rgba(0,0,0,.2);box-shadow:0 1px 1px rgba(0,0,0,.2)}.btn-primary.active,.btn-primary.focus,.btn-primary:active,.btn-primary:focus,.btn-primary:hover,.open>.dropdown-toggle.btn-primary{color:#fff;background-color:#3078eb;background-image:-webkit-linear-gradient(top,#4d90fe 0,#3078eb 100%);background-image:-o-linear-gradient(top,#4d90fe 0,#3078eb 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#4d90fe),to(#3078eb));background-image:linear-gradient(to bottom,#4d90fe 0,#3078eb 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff4d90fe',endColorstr='#ff3078eb',GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);background-repeat:repeat-x;border:1px solid #196aeb}.btn-primary.active,.btn-primary:active,.open>.dropdown-toggle.btn-primary{text-shadow:0 1px rgba(0,0,0,.3);background-image:-webkit-linear-gradient(top,#4d90fe 0,#1969e8 100%);background-image:-o-linear-gradient(top,#4d90fe 0,#1969e8 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#4d90fe),to(#1969e8));background-image:linear-gradient(to bottom,#4d90fe 0,#1969e8 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff4d90fe',endColorstr='#ff1969e8',GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);background-repeat:repeat-x;border:1px solid #135fd7;-webkit-box-shadow:inset 0 1px 2px rgba(0,0,0,.3);box-shadow:inset 0 1px 2px rgba(0,0,0,.3)}.btn-primary.focus,.btn-primary:focus{border:1px solid #3079ed;-webkit-box-shadow:inset 0 0 0 1px #fff;box-shadow:inset 0 0 0 1px #fff}.btn-primary.disabled.focus,.btn-primary.disabled:focus,.btn-primary.disabled:hover,.btn-primary[disabled].focus,.btn-primary[disabled]:focus,.btn-primary[disabled]:hover,fieldset[disabled] .btn-primary.focus,fieldset[disabled] .btn-primary:focus,fieldset[disabled] .btn-primary:hover{background-color:#4d90fe;background-image:-webkit-linear-gradient(top,#4d90fe 0,#4787ed 100%);background-image:-o-linear-gradient(top,#4d90fe 0,#4787ed 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#4d90fe),to(#4787ed));background-image:linear-gradient(to bottom,#4d90fe 0,#4787ed 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff4d90fe',endColorstr='#ff4787ed',GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);background-repeat:repeat-x;border:1px solid #3079ed;-webkit-box-shadow:none;box-shadow:none}.btn-primary .badge{color:#3079ed;background-color:#fff}.btn-success{color:#fff;text-shadow:0 1px rgba(0,0,0,.1);background-image:-webkit-linear-gradient(top,#35aa47 0,#35aa47 100%);background-image:-o-linear-gradient(top,#35aa47 0,#35aa47 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#35aa47),to(#35aa47));background-image:linear-gradient(to bottom,#35aa47 0,#35aa47 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff35aa47',endColorstr='#ff35aa47',GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);background-repeat:repeat-x;border:1px solid #359947}.btn-success:hover{text-shadow:0 1px rgba(0,0,0,.3);-webkit-box-shadow:0 1px 1px rgba(0,0,0,.2);box-shadow:0 1px 1px rgba(0,0,0,.2)}.btn-success.active,.btn-success.focus,.btn-success:active,.btn-success:focus,.btn-success:hover,.open>.dropdown-toggle.btn-success{color:#fff;background-color:#2f973f;background-image:-webkit-linear-gradient(top,#35aa47 0,#2f973f 100%);background-image:-o-linear-gradient(top,#35aa47 0,#2f973f 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#35aa47),to(#2f973f));background-image:linear-gradient(to bottom,#35aa47 0,#2f973f 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff35aa47',endColorstr='#ff2f973f',GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);background-repeat:repeat-x;border:1px solid #2e863e}.btn-success.active,.btn-success:active,.open>.dropdown-toggle.btn-success{text-shadow:0 1px rgba(0,0,0,.3);background-image:-webkit-linear-gradient(top,#35aa47 0,#298337 100%);background-image:-o-linear-gradient(top,#35aa47 0,#298337 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#35aa47),to(#298337));background-image:linear-gradient(to bottom,#35aa47 0,#298337 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff35aa47',endColorstr='#ff298337',GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);background-repeat:repeat-x;border:1px solid #287335;-webkit-box-shadow:inset 0 1px 2px rgba(0,0,0,.3);box-shadow:inset 0 1px 2px rgba(0,0,0,.3)}.btn-success.focus,.btn-success:focus{border:1px solid #359947;-webkit-box-shadow:inset 0 0 0 1px #fff;box-shadow:inset 0 0 0 1px #fff}.btn-success.disabled.focus,.btn-success.disabled:focus,.btn-success.disabled:hover,.btn-success[disabled].focus,.btn-success[disabled]:focus,.btn-success[disabled]:hover,fieldset[disabled] .btn-success.focus,fieldset[disabled] .btn-success:focus,fieldset[disabled] .btn-success:hover{background-color:#35aa47;background-image:-webkit-linear-gradient(top,#35aa47 0,#35aa47 100%);background-image:-o-linear-gradient(top,#35aa47 0,#35aa47 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#35aa47),to(#35aa47));background-image:linear-gradient(to bottom,#35aa47 0,#35aa47 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff35aa47',endColorstr='#ff35aa47',GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);background-repeat:repeat-x;border:1px solid #359947;-webkit-box-shadow:none;box-shadow:none}.btn-success .badge{color:#359947;background-color:#fff}.btn-info{color:#fff;text-shadow:0 1px rgba(0,0,0,.1);background-image:-webkit-linear-gradient(top,#5bc0de 0,#5bc0de 100%);background-image:-o-linear-gradient(top,#5bc0de 0,#5bc0de 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#5bc0de),to(#5bc0de));background-image:linear-gradient(to bottom,#5bc0de 0,#5bc0de 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5bc0de',endColorstr='#ff5bc0de',GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);background-repeat:repeat-x;border:1px solid #46b8da}.btn-info:hover{text-shadow:0 1px rgba(0,0,0,.3);-webkit-box-shadow:0 1px 1px rgba(0,0,0,.2);box-shadow:0 1px 1px rgba(0,0,0,.2)}.btn-info.active,.btn-info.focus,.btn-info:active,.btn-info:focus,.btn-info:hover,.open>.dropdown-toggle.btn-info{color:#fff;background-color:#46b8da;background-image:-webkit-linear-gradient(top,#5bc0de 0,#46b8da 100%);background-image:-o-linear-gradient(top,#5bc0de 0,#46b8da 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#5bc0de),to(#46b8da));background-image:linear-gradient(to bottom,#5bc0de 0,#46b8da 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5bc0de',endColorstr='#ff46b8da',GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);background-repeat:repeat-x;border:1px solid #31b0d5}.btn-info.active,.btn-info:active,.open>.dropdown-toggle.btn-info{text-shadow:0 1px rgba(0,0,0,.3);background-image:-webkit-linear-gradient(top,#5bc0de 0,#31b0d5 100%);background-image:-o-linear-gradient(top,#5bc0de 0,#31b0d5 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#5bc0de),to(#31b0d5));background-image:linear-gradient(to bottom,#5bc0de 0,#31b0d5 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5bc0de',endColorstr='#ff31b0d5',GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);background-repeat:repeat-x;border:1px solid #28a1c5;-webkit-box-shadow:inset 0 1px 2px rgba(0,0,0,.3);box-shadow:inset 0 1px 2px rgba(0,0,0,.3)}.btn-info.focus,.btn-info:focus{border:1px solid #46b8da;-webkit-box-shadow:inset 0 0 0 1px #fff;box-shadow:inset 0 0 0 1px #fff}.btn-info.disabled.focus,.btn-info.disabled:focus,.btn-info.disabled:hover,.btn-info[disabled].focus,.btn-info[disabled]:focus,.btn-info[disabled]:hover,fieldset[disabled] .btn-info.focus,fieldset[disabled] .btn-info:focus,fieldset[disabled] .btn-info:hover{background-color:#5bc0de;background-image:-webkit-linear-gradient(top,#5bc0de 0,#5bc0de 100%);background-image:-o-linear-gradient(top,#5bc0de 0,#5bc0de 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#5bc0de),to(#5bc0de));background-image:linear-gradient(to bottom,#5bc0de 0,#5bc0de 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5bc0de',endColorstr='#ff5bc0de',GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);background-repeat:repeat-x;border:1px solid #46b8da;-webkit-box-shadow:none;box-shadow:none}.btn-info .badge{color:#46b8da;background-color:#fff}.btn-warning{color:#fff;text-shadow:0 1px rgba(0,0,0,.1);background-image:-webkit-linear-gradient(top,#fbb450 0,#faa937 100%);background-image:-o-linear-gradient(top,#fbb450 0,#faa937 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#fbb450),to(#faa937));background-image:linear-gradient(to bottom,#fbb450 0,#faa937 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fffbb450',endColorstr='#fffaa937',GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);background-repeat:repeat-x;border:1px solid #faa328}.btn-warning:hover{text-shadow:0 1px rgba(0,0,0,.3);-webkit-box-shadow:0 1px 1px rgba(0,0,0,.2);box-shadow:0 1px 1px rgba(0,0,0,.2)}.btn-warning.active,.btn-warning.focus,.btn-warning:active,.btn-warning:focus,.btn-warning:hover,.open>.dropdown-toggle.btn-warning{color:#fff;background-color:#f99e1e;background-image:-webkit-linear-gradient(top,#fbb450 0,#f99e1e 100%);background-image:-o-linear-gradient(top,#fbb450 0,#f99e1e 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#fbb450),to(#f99e1e));background-image:linear-gradient(to bottom,#fbb450 0,#f99e1e 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fffbb450',endColorstr='#fff99e1e',GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);background-repeat:repeat-x;border:1px solid #f9980f}.btn-warning.active,.btn-warning:active,.open>.dropdown-toggle.btn-warning{text-shadow:0 1px rgba(0,0,0,.3);background-image:-webkit-linear-gradient(top,#fbb450 0,#f89306 100%);background-image:-o-linear-gradient(top,#fbb450 0,#f89306 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#fbb450),to(#f89306));background-image:linear-gradient(to bottom,#fbb450 0,#f89306 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fffbb450',endColorstr='#fff89306',GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);background-repeat:repeat-x;border:1px solid #e98b06;-webkit-box-shadow:inset 0 1px 2px rgba(0,0,0,.3);box-shadow:inset 0 1px 2px rgba(0,0,0,.3)}.btn-warning.focus,.btn-warning:focus{border:1px solid #faa328;-webkit-box-shadow:inset 0 0 0 1px #fff;box-shadow:inset 0 0 0 1px #fff}.btn-warning.disabled.focus,.btn-warning.disabled:focus,.btn-warning.disabled:hover,.btn-warning[disabled].focus,.btn-warning[disabled]:focus,.btn-warning[disabled]:hover,fieldset[disabled] .btn-warning.focus,fieldset[disabled] .btn-warning:focus,fieldset[disabled] .btn-warning:hover{background-color:#fbb450;background-image:-webkit-linear-gradient(top,#fbb450 0,#faa937 100%);background-image:-o-linear-gradient(top,#fbb450 0,#faa937 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#fbb450),to(#faa937));background-image:linear-gradient(to bottom,#fbb450 0,#faa937 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fffbb450',endColorstr='#fffaa937',GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);background-repeat:repeat-x;border:1px solid #faa328;-webkit-box-shadow:none;box-shadow:none}.btn-warning .badge{color:#faa328;background-color:#fff}.btn-danger{color:#fff;text-shadow:0 1px rgba(0,0,0,.1);background-image:-webkit-linear-gradient(top,#dd4b39 0,#d14836 100%);background-image:-o-linear-gradient(top,#dd4b39 0,#d14836 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#dd4b39),to(#d14836));background-image:linear-gradient(to bottom,#dd4b39 0,#d14836 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffdd4b39',endColorstr='#ffd14836',GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);background-repeat:repeat-x;border:1px solid #c6322a}.btn-danger:hover{text-shadow:0 1px rgba(0,0,0,.3);-webkit-box-shadow:0 1px 1px rgba(0,0,0,.2);box-shadow:0 1px 1px rgba(0,0,0,.2)}.btn-danger.active,.btn-danger.focus,.btn-danger:active,.btn-danger:focus,.btn-danger:hover,.open>.dropdown-toggle.btn-danger{color:#fff;background-color:#c13e2c;background-image:-webkit-linear-gradient(top,#dd4b39 0,#c13e2c 100%);background-image:-o-linear-gradient(top,#dd4b39 0,#c13e2c 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#dd4b39),to(#c13e2c));background-image:linear-gradient(to bottom,#dd4b39 0,#c13e2c 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffdd4b39',endColorstr='#ffc13e2c',GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);background-repeat:repeat-x;border:1px solid #b12d26}.btn-danger.active,.btn-danger:active,.open>.dropdown-toggle.btn-danger{text-shadow:0 1px rgba(0,0,0,.3);background-image:-webkit-linear-gradient(top,#dd4b39 0,#ad3727 100%);background-image:-o-linear-gradient(top,#dd4b39 0,#ad3727 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#dd4b39),to(#ad3727));background-image:linear-gradient(to bottom,#dd4b39 0,#ad3727 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffdd4b39',endColorstr='#ffad3727',GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);background-repeat:repeat-x;border:1px solid #9c2721;-webkit-box-shadow:inset 0 1px 2px rgba(0,0,0,.3);box-shadow:inset 0 1px 2px rgba(0,0,0,.3)}.btn-danger.focus,.btn-danger:focus{border:1px solid #c6322a;-webkit-box-shadow:inset 0 0 0 1px #fff;box-shadow:inset 0 0 0 1px #fff}.btn-danger.disabled.focus,.btn-danger.disabled:focus,.btn-danger.disabled:hover,.btn-danger[disabled].focus,.btn-danger[disabled]:focus,.btn-danger[disabled]:hover,fieldset[disabled] .btn-danger.focus,fieldset[disabled] .btn-danger:focus,fieldset[disabled] .btn-danger:hover{background-color:#dd4b39;background-image:-webkit-linear-gradient(top,#dd4b39 0,#d14836 100%);background-image:-o-linear-gradient(top,#dd4b39 0,#d14836 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#dd4b39),to(#d14836));background-image:linear-gradient(to bottom,#dd4b39 0,#d14836 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffdd4b39',endColorstr='#ffd14836',GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);background-repeat:repeat-x;border:1px solid #c6322a;-webkit-box-shadow:none;box-shadow:none}.btn-danger .badge{color:#c6322a;background-color:#fff}.btn-link{color:#15c}.btn-link,.btn-link.active,.btn-link:active,.btn-link[disabled],fieldset[disabled] .btn-link{background-color:transparent;-webkit-box-shadow:none;box-shadow:none}.btn-link,.btn-link.focus,.btn-link:active,.btn-link:focus,.btn-link:hover{border-color:transparent}.btn-link.focus,.btn-link:focus,.btn-link:hover{color:#15c;background-color:transparent;-webkit-box-shadow:none;box-shadow:none}.btn-link[disabled]:focus .btn-link[disabled].focus,.btn-link[disabled]:focus fieldset[disabled] .btn-link.focus,.btn-link[disabled]:hover,fieldset[disabled] .btn-link:focus .btn-link[disabled].focus,fieldset[disabled] .btn-link:focus fieldset[disabled] .btn-link.focus,fieldset[disabled] .btn-link:hover{color:#333}.btn-group-lg>.btn,.btn-lg{padding:9px 14px;font-size:14px;line-height:1.3;border-radius:2px}.btn-group-sm>.btn,.btn-sm{padding:3px 8px;font-size:12px;line-height:1.5;border-radius:2px}.btn-group-xs>.btn,.btn-xs{padding:2px 6px;font-size:11px;line-height:1.25;border-radius:1px}.dropdown-menu{padding:6px 0;margin:1px 0 0;font-size:13px;background-color:#fff;border:1px solid #ccc;border:1px solid rgba(0,0,0,.2);border-radius:0;-webkit-box-shadow:0 2px 4px rgba(0,0,0,.2);box-shadow:0 2px 4px rgba(0,0,0,.2)}.dropdown-menu .divider{height:1px;margin:8px 0;overflow:hidden;background-color:#ebebeb}.dropdown-menu>li>a{position:relative;padding:3px 30px}.dropdown-menu>li>a .glyphicon{position:absolute;top:4px;left:7px}.dropdown-menu li>a:focus,.dropdown-menu li>a:hover,.dropdown-submenu:focus>a,.dropdown-submenu:hover>a{color:#333;background-color:#eee;background-image:-webkit-linear-gradient(top,#eee 0,#eee 100%);background-image:-o-linear-gradient(top,#eee 0,#eee 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#eee),to(#eee));background-image:linear-gradient(to bottom,#eee 0,#eee 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffeeeeee',endColorstr='#ffeeeeee',GradientType=0);background-repeat:repeat-x}.dropdown-menu>.active>a,.dropdown-menu>.active>a:focus,.dropdown-menu>.active>a:hover{color:#333;background-color:#eee;background-image:-webkit-linear-gradient(top,#eee 0,#eee 100%);background-image:-o-linear-gradient(top,#eee 0,#eee 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#eee),to(#eee));background-image:linear-gradient(to bottom,#eee 0,#eee 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffeeeeee',endColorstr='#ffeeeeee',GradientType=0);background-repeat:repeat-x}.dropdown-header{color:#999}.dropdown-submenu{position:relative}.dropdown-submenu>.dropdown-menu{top:0;left:100%;margin-left:-1px;border-radius:0}.dropdown-submenu:hover>.dropdown-menu{display:block}.dropup .dropdown-submenu>.dropdown-menu{top:auto;bottom:0;margin-top:0;margin-bottom:-2px;border-radius:0}.dropdown-submenu>a:after{position:absolute;right:10px;margin-top:5px;content:""}.dropdown-submenu.dropdown-menu-left,.dropdown-submenu.pull-left{float:none!important}.dropdown-submenu.dropdown-menu-left>.dropdown-menu,.dropdown-submenu.pull-left>.dropdown-menu{left:-100%;margin-left:18px;border-radius:0}.btn-group-vertical>.btn:focus,.btn-group>.btn:focus{z-index:3}.btn-toolbar>.btn,.btn-toolbar>.btn-group,.btn-toolbar>.input-group{margin-left:16px}.btn-group>.btn+.dropdown-toggle{-webkit-box-shadow:none;box-shadow:none}.btn-group>.dropdown-toggle:hover{-webkit-box-shadow:0 1px 1px rgba(0,0,0,.1);box-shadow:0 1px 1px rgba(0,0,0,.1)}.btn-group>.btn-danger.dropdown-toggle:hover,.btn-group>.btn-info.dropdown-toggle:hover,.btn-group>.btn-primary.dropdown-toggle:hover,.btn-group>.btn-success.dropdown-toggle:hover,.btn-group>.btn-warning.dropdown-toggle:hover{-webkit-box-shadow:0 1px 1px rgba(0,0,0,.2);box-shadow:0 1px 1px rgba(0,0,0,.2)}.btn-group>.btn.dropdown-toggle.active,.btn-group>.btn.dropdown-toggle:active{-webkit-box-shadow:inset 0 1px 2px rgba(0,0,0,.1);box-shadow:inset 0 1px 2px rgba(0,0,0,.1)}.btn-group>.btn-danger.dropdown-toggle.active,.btn-group>.btn-danger.dropdown-toggle:active,.btn-group>.btn-info.dropdown-toggle.active,.btn-group>.btn-info.dropdown-toggle:active,.btn-group>.btn-primary.dropdown-toggle.active,.btn-group>.btn-primary.dropdown-toggle:active,.btn-group>.btn-success.dropdown-toggle.active,.btn-group>.btn-success.dropdown-toggle:active,.btn-group>.btn-warning.dropdown-toggle.active,.btn-group>.btn-warning.dropdown-toggle:active{-webkit-box-shadow:inset 0 1px 2px rgba(0,0,0,.3);box-shadow:inset 0 1px 2px rgba(0,0,0,.3)}.btn-group>.btn-sm.dropdown-toggle{padding:5px 7px}.btn-group>.btn-lg.dropdown-toggle{padding:9px 9px}.btn-group.open .dropdown-toggle{-webkit-box-shadow:inset 0 1px 6px rgba(0,0,0,.15);box-shadow:inset 0 1px 6px rgba(0,0,0,.15)}.btn-group.open .btn.dropdown-toggle{background-color:#f3f3f3;background-image:none;-webkit-box-shadow:inset 0 1px 2px rgba(0,0,0,.1);box-shadow:inset 0 1px 2px rgba(0,0,0,.1)}.btn-group.open .btn-primary.dropdown-toggle{background-color:#4d90fe;background-image:none;-webkit-box-shadow:inset 0 1px 2px rgba(0,0,0,.3);box-shadow:inset 0 1px 2px rgba(0,0,0,.3)}.btn-group.open .btn-warning.dropdown-toggle{background-color:#faa937;background-image:none;-webkit-box-shadow:inset 0 1px 2px rgba(0,0,0,.3);box-shadow:inset 0 1px 2px rgba(0,0,0,.3)}.btn-group.open .btn-danger.dropdown-toggle{background-color:#d84a38;background-image:none;-webkit-box-shadow:inset 0 1px 2px rgba(0,0,0,.3);box-shadow:inset 0 1px 2px rgba(0,0,0,.3)}.btn-group.open .btn-success.dropdown-toggle{background-color:#35aa47;background-image:none;-webkit-box-shadow:inset 0 1px 2px rgba(0,0,0,.3);box-shadow:inset 0 1px 2px rgba(0,0,0,.3)}.btn-group.open .btn-info.dropdown-toggle{background-color:#5bc0de;background-image:none;-webkit-box-shadow:inset 0 1px 2px rgba(0,0,0,.3);box-shadow:inset 0 1px 2px rgba(0,0,0,.3)}.btn-lg .caret{border-width:5px 5px 0}.dropup .btn-lg .caret{border-width:0 5px 5px}.btn-group-vertical>.btn:first-child:not(:last-child){border-top-left-radius:2px;border-top-right-radius:2px}.btn-group-vertical>.btn:last-child:not(:first-child){border-bottom-right-radius:2px;border-bottom-left-radius:2px}.input-group-lg>.form-control,.input-group-lg>.input-group-addon,.input-group-lg>.input-group-btn>.btn{height:38px;padding:9px 14px;font-size:14px;line-height:1.3;border-radius:1px}select.input-group-lg>.form-control,select.input-group-lg>.input-group-addon,select.input-group-lg>.input-group-btn>.btn{height:38px;line-height:38px}select[multiple].input-group-lg>.form-control,select[multiple].input-group-lg>.input-group-addon,select[multiple].input-group-lg>.input-group-btn>.btn,textarea.input-group-lg>.form-control,textarea.input-group-lg>.input-group-addon,textarea.input-group-lg>.input-group-btn>.btn{height:auto}.input-group-sm>.form-control,.input-group-sm>.input-group-addon,.input-group-sm>.input-group-btn>.btn{height:26px;padding:3px 8px;font-size:12px;line-height:1.5;border-radius:1px}select.input-group-sm>.form-control,select.input-group-sm>.input-group-addon,select.input-group-sm>.input-group-btn>.btn{height:26px;line-height:26px}select[multiple].input-group-sm>.form-control,select[multiple].input-group-sm>.input-group-addon,select[multiple].input-group-sm>.input-group-btn>.btn,textarea.input-group-sm>.form-control,textarea.input-group-sm>.input-group-addon,textarea.input-group-sm>.input-group-btn>.btn{height:auto}.input-group .form-control,.input-group-addon,.input-group-btn{margin:0;border-radius:0}.input-group-addon{padding:5px 8px;font-size:13px;color:#555;border:1px solid #d9d9d9;border-top-color:silver;border-radius:2px}.input-group-addon.input-sm{padding:3px 8px;font-size:12px;border-radius:1px}.input-group-addon.input-lg{padding:9px 14px;font-size:14px;border-radius:1px}.input-group-addon input[type=radio],.input-group-addon input[type=checkbox]{margin-bottom:-3px}.nav>li.disabled>a{color:#999}.nav>li.disabled>a:focus,.nav>li.disabled>a:hover{color:#999}.nav .open>a,.nav .open>a:focus,.nav .open>a:hover{color:#fff;background-color:#999;border-color:#999}.nav-tabs>li>a{color:#666;border-radius:2px 2px 0 0}.nav-tabs>li>a:hover{border-color:#eee #eee #ddd}.nav-tabs>li.active>a,.nav-tabs>li.active>a:focus,.nav-tabs>li.active>a:hover{font-weight:700;color:#333}.nav-tabs-google>li{margin:0 -1px 0 0}.nav-tabs-google>li>a{padding:12px 8px;margin:0 8px;line-height:1.4;color:#777;border:3px solid transparent;border-width:3px 0;border-radius:0}.nav-tabs-google>li>a:first-of-type{margin-left:0}.nav-tabs-google>li>a:focus,.nav-tabs-google>li>a:hover{background-color:transparent;border-top-color:transparent}.nav-tabs-google>li>a:hover{color:#000;border-bottom-color:transparent}.nav-tabs-google>li>a:active{color:#dd4b39}.nav-tabs-google>li>a:focus{color:#000;outline:0}.nav-tabs-google>li.active>a,.nav-tabs-google>li.active>a:focus,.nav-tabs-google>li.active>a:hover{color:#dd4b39;border:3px solid transparent;border-width:3px 0;border-bottom-color:#dd4b39}.nav-pills>li>a{border-radius:2px}.nav-pills>li.active>a,.nav-pills>li.active>a:focus,.nav-pills>li.active>a:hover{color:#fff;background-color:#4d90fe}.navbar{min-height:28px;margin-bottom:18px}@media (min-width:768px){.navbar{border-radius:2px}}.navbar-brand{height:28px;padding:5px 15px;font-size:14px;line-height:18px}.navbar-brand>.glyphicon{margin-top:0}@media (min-width:768px){.navbar>.container .navbar-brand,.navbar>.container-fluid .navbar-brand{margin-left:-15px}}.navbar-toggle{padding:5px 10px;margin-top:1px;margin-right:15px;margin-bottom:1px;border-radius:2px}.navbar-nav{margin:2px -15px}.navbar-nav>li>a{padding-top:5px;padding-bottom:5px;line-height:18px}@media (max-width:767px){.navbar-nav .open .dropdown-menu>li>a{line-height:18px}}@media (min-width:768px){.navbar-nav{margin:0}.navbar-nav>li>a{padding-top:5px;padding-bottom:5px}}.navbar-form{padding:10px 15px;margin-top:0;margin-right:-15px;margin-bottom:0;margin-left:-15px;-webkit-box-shadow:none;box-shadow:none}.navbar-form>.input-group .form-control{margin-top:1px;margin-bottom:1px}@media (min-width:768px){.navbar-form{padding-top:0;padding-bottom:0;margin-right:0;margin-left:0}}.navbar-form .form-control{height:26px;padding:3px 8px}.navbar .btn,.navbar-btn{padding:3px 8px;margin-top:1px;margin-bottom:1px}.navbar .btn.btn-sm,.navbar-btn.btn-sm{margin-top:1px;margin-bottom:1px}.navbar .btn.btn-xs,.navbar-btn.btn-xs{padding:2px 6px;margin-top:4px;margin-bottom:4px}.navbar-text{margin-top:5px;margin-bottom:5px}.navbar-default{background-color:#2d2d2d;border-color:#000}.navbar-default .navbar-brand{color:#999}.navbar-default .navbar-brand:focus,.navbar-default .navbar-brand:hover{color:#fff;background-color:transparent}.navbar-default .navbar-brand>.caret{border-top-color:#999;border-bottom-color:#999}.navbar-default .navbar-text{color:#999}.navbar-default .navbar-nav>li>a{color:#999}.navbar-default .navbar-nav>li>a:focus,.navbar-default .navbar-nav>li>a:hover{color:#fff;background-color:transparent}.navbar-default .navbar-nav>.active>a,.navbar-default .navbar-nav>.active>a:focus,.navbar-default .navbar-nav>.active>a:hover{color:#fff;background-color:#141414}.navbar-default .navbar-nav>.disabled>a,.navbar-default .navbar-nav>.disabled>a:focus,.navbar-default .navbar-nav>.disabled>a:hover{color:#555;background-color:transparent}.navbar-default .navbar-toggle{border-color:#222}.navbar-default .navbar-toggle:focus,.navbar-default .navbar-toggle:hover{background-color:#333}.navbar-default .navbar-toggle .icon-bar{background-color:#fff}.navbar-default .navbar-collapse,.navbar-default .navbar-form{border-color:#000}.navbar-default .navbar-nav>.open>a,.navbar-default .navbar-nav>.open>a:focus,.navbar-default .navbar-nav>.open>a:hover{color:#fff;background-color:#141414}@media (max-width:767px){.navbar-default .navbar-nav .open .dropdown-menu>li>a{color:#999}.navbar-default .navbar-nav .open .dropdown-menu>li>a:focus,.navbar-default .navbar-nav .open .dropdown-menu>li>a:hover{color:#fff;background-color:transparent}.navbar-default .navbar-nav .open .dropdown-menu>.active>a,.navbar-default .navbar-nav .open .dropdown-menu>.active>a:focus,.navbar-default .navbar-nav .open .dropdown-menu>.active>a:hover{color:#fff;background-color:#141414}.navbar-default .navbar-nav .open .dropdown-menu>.disabled>a,.navbar-default .navbar-nav .open .dropdown-menu>.disabled>a:focus,.navbar-default .navbar-nav .open .dropdown-menu>.disabled>a:hover{color:#555;background-color:transparent}}.navbar-default .navbar-link{color:#999}.navbar-default .navbar-link:hover{color:#fff}.navbar-default .btn-link{color:#999}.navbar-default .btn-link:focus,.navbar-default .btn-link:hover{color:#fff}.navbar-default .btn-link[disabled]:focus,.navbar-default .btn-link[disabled]:hover,fieldset[disabled] .navbar-default .btn-link:focus,fieldset[disabled] .navbar-default .btn-link:hover{color:#555}.navbar-inverse{background-color:#fafafa;border-color:#dbdbdb}.navbar-inverse .navbar-brand{color:#999}.navbar-inverse .navbar-brand:focus,.navbar-inverse .navbar-brand:hover{color:grey;background-color:transparent}.navbar-inverse .navbar-brand>.caret{border-top-color:#999;border-bottom-color:#999}.navbar-inverse .navbar-text{color:#999}.navbar-inverse .navbar-nav>li>a{color:#999}.navbar-inverse .navbar-nav>li>a:focus,.navbar-inverse .navbar-nav>li>a:hover{color:#333;background-color:transparent}.navbar-inverse .navbar-nav>.active>a,.navbar-inverse .navbar-nav>.active>a:focus,.navbar-inverse .navbar-nav>.active>a:hover{color:#333;background-color:#e1e1e1}.navbar-inverse .navbar-nav>.disabled>a,.navbar-inverse .navbar-nav>.disabled>a:focus,.navbar-inverse .navbar-nav>.disabled>a:hover{color:#ccc;background-color:transparent}.navbar-inverse .navbar-toggle{border-color:#ddd}.navbar-inverse .navbar-toggle:focus,.navbar-inverse .navbar-toggle:hover{background-color:#ddd}.navbar-inverse .navbar-toggle .icon-bar{background-color:#888}.navbar-inverse .navbar-collapse,.navbar-inverse .navbar-form{border-color:#e8e8e8}.navbar-inverse .navbar-nav>.open>a,.navbar-inverse .navbar-nav>.open>a:focus,.navbar-inverse .navbar-nav>.open>a:hover{color:#333;background-color:#e1e1e1}@media (max-width:767px){.navbar-inverse .navbar-nav .open .dropdown-menu>.dropdown-header{border-color:#dbdbdb}.navbar-inverse .navbar-nav .open .dropdown-menu .divider{background-color:#dbdbdb}.navbar-inverse .navbar-nav .open .dropdown-menu>li>a{color:#999}.navbar-inverse .navbar-nav .open .dropdown-menu>li>a:focus,.navbar-inverse .navbar-nav .open .dropdown-menu>li>a:hover{color:#333;background-color:transparent}.navbar-inverse .navbar-nav .open .dropdown-menu>.active>a,.navbar-inverse .navbar-nav .open .dropdown-menu>.active>a:focus,.navbar-inverse .navbar-nav .open .dropdown-menu>.active>a:hover{color:#333;background-color:#e1e1e1}.navbar-inverse .navbar-nav .open .dropdown-menu>.disabled>a,.navbar-inverse .navbar-nav .open .dropdown-menu>.disabled>a:focus,.navbar-inverse .navbar-nav .open .dropdown-menu>.disabled>a:hover{color:#ccc;background-color:transparent}}.navbar-inverse .navbar-link{color:#999}.navbar-inverse .navbar-link:hover{color:#333}.navbar-inverse .btn-link{color:#999}.navbar-inverse .btn-link:focus,.navbar-inverse .btn-link:hover{color:#333}.navbar-inverse .btn-link[disabled]:focus,.navbar-inverse .btn-link[disabled]:hover,fieldset[disabled] .navbar-inverse .btn-link:focus,fieldset[disabled] .navbar-inverse .btn-link:hover{color:#ccc}.navbar-masthead{min-height:44px;margin-bottom:18px}@media (min-width:768px){.navbar-masthead{border-radius:2px}}.navbar-masthead .navbar-static-top{z-index:1005}.navbar-masthead .navbar-fixed-bottom,.navbar-masthead .navbar-fixed-top{z-index:1029}.navbar-masthead .navbar-brand{height:44px;padding:13px 15px;font-size:20px}.navbar-masthead .navbar-brand>.glyphicon{margin-top:-3px}@media (min-width:768px){.navbar>.container .navbar-masthead .navbar-brand,.navbar>.container-fluid .navbar-masthead .navbar-brand{margin-left:-15px}}.navbar-masthead .navbar-toggle{margin-top:7px;margin-right:15px;margin-bottom:7px}.navbar-masthead .navbar-nav{margin:6px -15px}@media (min-width:768px){.navbar-masthead .navbar-nav{margin:6px 0}.navbar-masthead .navbar-nav>li>a{padding-top:8px;padding-bottom:6px}}.navbar-masthead .navbar-form{padding:10px 15px;margin-top:0;margin-right:-15px;margin-bottom:0;margin-left:-15px}.navbar-masthead .navbar-form>.input-group .form-control{margin-top:7px;margin-bottom:7px}@media (max-width:767px){.navbar-masthead .navbar-form .form-group{margin-bottom:5px}}@media (min-width:768px){.navbar-masthead .navbar-form{padding-top:0;padding-bottom:0;margin-right:0;margin-left:0}}.navbar-masthead .navbar-form .form-control{height:30px;padding:5px 8px}.navbar-masthead.navbar .btn,.navbar-masthead.navbar-btn{padding:5px 8px;margin-top:7px;margin-bottom:7px}.navbar-masthead.navbar .btn.btn-sm,.navbar-masthead.navbar-btn.btn-sm{padding:3px 8px;margin-top:9px;margin-bottom:9px}.navbar-masthead.navbar .btn.btn-xs,.navbar-masthead.navbar-btn.btn-xs{padding:2px 6px;margin-top:12px;margin-bottom:12px}.navbar-masthead .navbar-text{margin-top:13px;margin-bottom:13px}.navbar-masthead.navbar-default{background-color:#f1f1f1;border-color:#e5e5e5}.navbar-masthead.navbar-default .navbar-brand{color:#777}.navbar-masthead.navbar-default .navbar-brand:focus,.navbar-masthead.navbar-default .navbar-brand:hover{color:#777;background-color:transparent}.navbar-masthead.navbar-default .navbar-brand>.caret{border-top-color:#777;border-bottom-color:#777}.navbar-masthead.navbar-default .navbar-text{color:#777}.navbar-masthead.navbar-default .navbar-nav>li>a{color:#777}.navbar-masthead.navbar-default .navbar-nav>li>a:focus,.navbar-masthead.navbar-default .navbar-nav>li>a:hover{color:#333;background-color:transparent}.navbar-masthead.navbar-default .navbar-nav>.active>a,.navbar-masthead.navbar-default .navbar-nav>.active>a:focus,.navbar-masthead.navbar-default .navbar-nav>.active>a:hover{color:#333;background-color:#f1f1f1}.navbar-masthead.navbar-default .navbar-nav>.disabled>a,.navbar-masthead.navbar-default .navbar-nav>.disabled>a:focus,.navbar-masthead.navbar-default .navbar-nav>.disabled>a:hover{color:#bbb;background-color:transparent}.navbar-masthead.navbar-default .navbar-toggle{border-color:#dcdcdc}.navbar-masthead.navbar-default .navbar-toggle:focus,.navbar-masthead.navbar-default .navbar-toggle:hover{background-color:#e4e4e4}.navbar-masthead.navbar-default .navbar-toggle .icon-bar{background-color:#888}.navbar-masthead.navbar-default .navbar-collapse,.navbar-masthead.navbar-default .navbar-form{border-color:#dfdfdf}.navbar-masthead.navbar-default .navbar-nav>.open>a,.navbar-masthead.navbar-default .navbar-nav>.open>a:focus,.navbar-masthead.navbar-default .navbar-nav>.open>a:hover{color:#333;background-color:#f1f1f1}@media (max-width:767px){.navbar-masthead.navbar-default .navbar-nav .open .dropdown-menu>li>a{color:#777}.navbar-masthead.navbar-default .navbar-nav .open .dropdown-menu>li>a:focus,.navbar-masthead.navbar-default .navbar-nav .open .dropdown-menu>li>a:hover{color:#333;background-color:transparent}.navbar-masthead.navbar-default .navbar-nav .open .dropdown-menu>.active>a,.navbar-masthead.navbar-default .navbar-nav .open .dropdown-menu>.active>a:focus,.navbar-masthead.navbar-default .navbar-nav .open .dropdown-menu>.active>a:hover{color:#333;background-color:#f1f1f1}.navbar-masthead.navbar-default .navbar-nav .open .dropdown-menu>.disabled>a,.navbar-masthead.navbar-default .navbar-nav .open .dropdown-menu>.disabled>a:focus,.navbar-masthead.navbar-default .navbar-nav .open .dropdown-menu>.disabled>a:hover{color:#bbb;background-color:transparent}}.navbar-masthead.navbar-default .navbar-link{color:#777}.navbar-masthead.navbar-default .navbar-link:hover{color:#333}.navbar-masthead.navbar-default .btn-link{color:#777}.navbar-masthead.navbar-default .btn-link:focus,.navbar-masthead.navbar-default .btn-link:hover{color:#333}.navbar-masthead.navbar-default .btn-link[disabled]:focus,.navbar-masthead.navbar-default .btn-link[disabled]:hover,fieldset[disabled] .navbar-masthead.navbar-default .btn-link:focus,fieldset[disabled] .navbar-masthead.navbar-default .btn-link:hover{color:#bbb}.navbar-masthead.navbar-inverse{background-color:#444;border-color:#333}.navbar-masthead.navbar-inverse .navbar-brand{color:#fff}.navbar-masthead.navbar-inverse .navbar-brand:focus,.navbar-masthead.navbar-inverse .navbar-brand:hover{color:#fff;background-color:transparent}.navbar-masthead.navbar-inverse .navbar-brand>.caret{border-top-color:#fff;border-bottom-color:#fff}.navbar-masthead.navbar-inverse .navbar-text{color:#999}.navbar-masthead.navbar-inverse .navbar-nav>li>a{color:#fff}.navbar-masthead.navbar-inverse .navbar-nav>li>a:focus,.navbar-masthead.navbar-inverse .navbar-nav>li>a:hover{color:#bbb;background-color:transparent}.navbar-masthead.navbar-inverse .navbar-nav>.active>a,.navbar-masthead.navbar-inverse .navbar-nav>.active>a:focus,.navbar-masthead.navbar-inverse .navbar-nav>.active>a:hover{color:#bbb;background-color:#444}.navbar-masthead.navbar-inverse .navbar-nav>.disabled>a,.navbar-masthead.navbar-inverse .navbar-nav>.disabled>a:focus,.navbar-masthead.navbar-inverse .navbar-nav>.disabled>a:hover{color:#777;background-color:transparent}.navbar-masthead.navbar-inverse .navbar-toggle{border-color:#222}.navbar-masthead.navbar-inverse .navbar-toggle:focus,.navbar-masthead.navbar-inverse .navbar-toggle:hover{background-color:#333}.navbar-masthead.navbar-inverse .navbar-toggle .icon-bar{background-color:#fff}.navbar-masthead.navbar-inverse .navbar-collapse,.navbar-masthead.navbar-inverse .navbar-form{border-color:#323232}.navbar-masthead.navbar-inverse .navbar-nav>.open>a,.navbar-masthead.navbar-inverse .navbar-nav>.open>a:focus,.navbar-masthead.navbar-inverse .navbar-nav>.open>a:hover{color:#bbb;background-color:#444}@media (max-width:767px){.navbar-masthead.navbar-inverse .navbar-nav .open .dropdown-menu>.dropdown-header{border-color:#333}.navbar-masthead.navbar-inverse .navbar-nav .open .dropdown-menu .divider{background-color:#333}.navbar-masthead.navbar-inverse .navbar-nav .open .dropdown-menu>li>a{color:#fff}.navbar-masthead.navbar-inverse .navbar-nav .open .dropdown-menu>li>a:focus,.navbar-masthead.navbar-inverse .navbar-nav .open .dropdown-menu>li>a:hover{color:#bbb;background-color:transparent}.navbar-masthead.navbar-inverse .navbar-nav .open .dropdown-menu>.active>a,.navbar-masthead.navbar-inverse .navbar-nav .open .dropdown-menu>.active>a:focus,.navbar-masthead.navbar-inverse .navbar-nav .open .dropdown-menu>.active>a:hover{color:#bbb;background-color:#444}.navbar-masthead.navbar-inverse .navbar-nav .open .dropdown-menu>.disabled>a,.navbar-masthead.navbar-inverse .navbar-nav .open .dropdown-menu>.disabled>a:focus,.navbar-masthead.navbar-inverse .navbar-nav .open .dropdown-menu>.disabled>a:hover{color:#777;background-color:transparent}}.navbar-masthead.navbar-inverse .navbar-link{color:#fff}.navbar-masthead.navbar-inverse .navbar-link:hover{color:#bbb}.navbar-masthead.navbar-inverse .btn-link{color:#fff}.navbar-masthead.navbar-inverse .btn-link:focus,.navbar-masthead.navbar-inverse .btn-link:hover{color:#bbb}.navbar-masthead.navbar-inverse .btn-link[disabled]:focus,.navbar-masthead.navbar-inverse .btn-link[disabled]:hover,fieldset[disabled] .navbar-masthead.navbar-inverse .btn-link:focus,fieldset[disabled] .navbar-masthead.navbar-inverse .btn-link:hover{color:#777}.navbar-toolbar{min-height:36px;margin-bottom:18px}@media (min-width:768px){.navbar-toolbar{border-radius:2px}}.navbar-toolbar .navbar-static-top{z-index:1008}.navbar-toolbar .navbar-fixed-bottom,.navbar-toolbar .navbar-fixed-top{z-index:1028}.navbar-toolbar .navbar-brand{height:36px;padding:9px 15px;font-size:16px;font-weight:700}@media (min-width:768px){.navbar>.container .navbar-toolbar .navbar-brand,.navbar>.container-fluid .navbar-toolbar .navbar-brand{margin-left:-15px}}.navbar-toolbar .navbar-toggle{margin-top:3px;margin-right:15px;margin-bottom:3px}.navbar-toolbar .navbar-nav{margin:4px -15px}.navbar-toolbar .navbar-nav>li{position:relative}.navbar-toolbar .navbar-nav>li>a{padding:9px 15px}.navbar-toolbar .navbar-nav>li>a:focus,.navbar-toolbar .navbar-nav>li>a:hover{text-decoration:underline}.navbar-toolbar .navbar-nav>li>.dropdown-menu{margin-top:1px}.navbar-toolbar .navbar-nav>.active>a{font-weight:700}.navbar-toolbar .navbar-nav>.active>a:before{position:absolute;bottom:-1px;left:50%;display:inline-block;margin-left:-8px;content:'';border-right:8px solid transparent;border-bottom:8px solid transparent;border-left:8px solid transparent}.navbar-toolbar .navbar-nav>.active>a:after{position:absolute;bottom:-1px;left:50%;display:inline-block;margin-left:-7px;content:'';border-right:7px solid transparent;border-bottom:7px solid transparent;border-left:7px solid transparent}@media (min-width:768px){.navbar-toolbar .navbar-nav{margin:0}.navbar-toolbar .navbar-nav>li>a{padding-top:9px;padding-bottom:9px}}.navbar-toolbar .navbar-form{padding:10px 15px;margin-top:0;margin-right:-15px;margin-bottom:0;margin-left:-15px}.navbar-toolbar .navbar-form>.input-group .form-control{margin-top:3px;margin-bottom:3px}@media (max-width:767px){.navbar-toolbar .navbar-form .form-group{margin-bottom:5px}}@media (min-width:768px){.navbar-toolbar .navbar-form{padding-top:0;padding-bottom:0;margin-right:0;margin-left:0}}.navbar-toolbar .navbar-form .form-control{height:30px;padding:5px 8px}.navbar-toolbar .dropdown-menu{border-top:1px none}.navbar-toolbar.navbar .btn,.navbar-toolbar.navbar-btn{padding:5px 8px;margin-top:3px;margin-bottom:3px}.navbar-toolbar.navbar .btn.btn-sm,.navbar-toolbar.navbar-btn.btn-sm{padding:3px 8px;margin-top:5px;margin-bottom:5px}.navbar-toolbar.navbar .btn.btn-xs,.navbar-toolbar.navbar-btn.btn-xs{padding:2px 6px;margin-top:8px;margin-bottom:8px}.navbar-toolbar .navbar-text{margin-top:9px;margin-bottom:9px}.navbar-toolbar.navbar-default{background-color:#fff;border-color:#ebebeb}.navbar-toolbar.navbar-default .navbar-brand{color:#dd4b39}.navbar-toolbar.navbar-default .navbar-brand:focus,.navbar-toolbar.navbar-default .navbar-brand:hover{color:#dd4b39;background-color:transparent}.navbar-toolbar.navbar-default .navbar-brand>.caret{border-top-color:#dd4b39;border-bottom-color:#dd4b39}.navbar-toolbar.navbar-default .navbar-text{color:#777}.navbar-toolbar.navbar-default .navbar-nav>li>a{color:#777}.navbar-toolbar.navbar-default .navbar-nav>li>a:focus,.navbar-toolbar.navbar-default .navbar-nav>li>a:hover{color:#333;background-color:transparent}.navbar-toolbar.navbar-default .navbar-nav>.active>a,.navbar-toolbar.navbar-default .navbar-nav>.active>a:focus,.navbar-toolbar.navbar-default .navbar-nav>.active>a:hover{color:#333;background-color:#f2f2f2}.navbar-toolbar.navbar-default .navbar-nav>.active>a:before{border-bottom:8px solid #ebebeb}.navbar-toolbar.navbar-default .navbar-nav>.active>a:after{border-bottom:7px solid #fff}.navbar-toolbar.navbar-default .navbar-nav>.disabled>a,.navbar-toolbar.navbar-default .navbar-nav>.disabled>a:focus,.navbar-toolbar.navbar-default .navbar-nav>.disabled>a:hover{color:#bbb;background-color:transparent}.navbar-toolbar.navbar-default .navbar-toggle{border-color:#dcdcdc}.navbar-toolbar.navbar-default .navbar-toggle:focus,.navbar-toolbar.navbar-default .navbar-toggle:hover{background-color:#e4e4e4}.navbar-toolbar.navbar-default .navbar-toggle .icon-bar{background-color:#888}.navbar-toolbar.navbar-default .navbar-collapse,.navbar-toolbar.navbar-default .navbar-form{border-color:#ededed}.navbar-toolbar.navbar-default .navbar-nav>.open>a,.navbar-toolbar.navbar-default .navbar-nav>.open>a:focus,.navbar-toolbar.navbar-default .navbar-nav>.open>a:hover{color:#333;background-color:#f2f2f2}@media (max-width:767px){.navbar-toolbar.navbar-default .navbar-nav .open .dropdown-menu>li>a{color:#777}.navbar-toolbar.navbar-default .navbar-nav .open .dropdown-menu>li>a:focus,.navbar-toolbar.navbar-default .navbar-nav .open .dropdown-menu>li>a:hover{color:#333;background-color:transparent}.navbar-toolbar.navbar-default .navbar-nav .open .dropdown-menu>.active>a,.navbar-toolbar.navbar-default .navbar-nav .open .dropdown-menu>.active>a:focus,.navbar-toolbar.navbar-default .navbar-nav .open .dropdown-menu>.active>a:hover{color:#333;background-color:#f2f2f2}.navbar-toolbar.navbar-default .navbar-nav .open .dropdown-menu>.disabled>a,.navbar-toolbar.navbar-default .navbar-nav .open .dropdown-menu>.disabled>a:focus,.navbar-toolbar.navbar-default .navbar-nav .open .dropdown-menu>.disabled>a:hover{color:#bbb;background-color:transparent}}.navbar-toolbar.navbar-default .navbar-link{color:#777}.navbar-toolbar.navbar-default .navbar-link:hover{color:#333}.navbar-toolbar.navbar-default .btn-link{color:#777}.navbar-toolbar.navbar-default .btn-link:focus,.navbar-toolbar.navbar-default .btn-link:hover{color:#333}.navbar-toolbar.navbar-default .btn-link[disabled]:focus,.navbar-toolbar.navbar-default .btn-link[disabled]:hover,fieldset[disabled] .navbar-toolbar.navbar-default .btn-link:focus,fieldset[disabled] .navbar-toolbar.navbar-default .btn-link:hover{color:#bbb}.navbar-toolbar.navbar-inverse{background-color:#444;border-color:#333}.navbar-toolbar.navbar-inverse .navbar-brand{color:#fff}.navbar-toolbar.navbar-inverse .navbar-brand:focus,.navbar-toolbar.navbar-inverse .navbar-brand:hover{color:#fff;background-color:transparent}.navbar-toolbar.navbar-inverse .navbar-brand>.caret{border-top-color:#fff;border-bottom-color:#fff}.navbar-toolbar.navbar-inverse .navbar-text{color:#999}.navbar-toolbar.navbar-inverse .navbar-nav>li>a{color:#fff}.navbar-toolbar.navbar-inverse .navbar-nav>li>a:focus,.navbar-toolbar.navbar-inverse .navbar-nav>li>a:hover{color:#fff;background-color:transparent}.navbar-toolbar.navbar-inverse .navbar-nav>.active>a,.navbar-toolbar.navbar-inverse .navbar-nav>.active>a:focus,.navbar-toolbar.navbar-inverse .navbar-nav>.active>a:hover{color:#fff;background-color:#444}.navbar-toolbar.navbar-inverse .navbar-nav>.active>a:before{border-bottom:8px solid #333}.navbar-toolbar.navbar-inverse .navbar-nav>.active>a:after{border-bottom:7px solid #fff}.navbar-toolbar.navbar-inverse .navbar-nav>.disabled>a,.navbar-toolbar.navbar-inverse .navbar-nav>.disabled>a:focus,.navbar-toolbar.navbar-inverse .navbar-nav>.disabled>a:hover{color:#777;background-color:transparent}.navbar-toolbar.navbar-inverse .navbar-toggle{border-color:#222}.navbar-toolbar.navbar-inverse .navbar-toggle:focus,.navbar-toolbar.navbar-inverse .navbar-toggle:hover{background-color:#333}.navbar-toolbar.navbar-inverse .navbar-toggle .icon-bar{background-color:#fff}.navbar-toolbar.navbar-inverse .navbar-collapse,.navbar-toolbar.navbar-inverse .navbar-form{border-color:#323232}.navbar-toolbar.navbar-inverse .navbar-nav>.open>a,.navbar-toolbar.navbar-inverse .navbar-nav>.open>a:focus,.navbar-toolbar.navbar-inverse .navbar-nav>.open>a:hover{color:#fff;background-color:#444}@media (max-width:767px){.navbar-toolbar.navbar-inverse .navbar-nav .open .dropdown-menu>.dropdown-header{border-color:#333}.navbar-toolbar.navbar-inverse .navbar-nav .open .dropdown-menu .divider{background-color:#333}.navbar-toolbar.navbar-inverse .navbar-nav .open .dropdown-menu>li>a{color:#fff}.navbar-toolbar.navbar-inverse .navbar-nav .open .dropdown-menu>li>a:focus,.navbar-toolbar.navbar-inverse .navbar-nav .open .dropdown-menu>li>a:hover{color:#fff;background-color:transparent}.navbar-toolbar.navbar-inverse .navbar-nav .open .dropdown-menu>.active>a,.navbar-toolbar.navbar-inverse .navbar-nav .open .dropdown-menu>.active>a:focus,.navbar-toolbar.navbar-inverse .navbar-nav .open .dropdown-menu>.active>a:hover{color:#fff;background-color:#444}.navbar-toolbar.navbar-inverse .navbar-nav .open .dropdown-menu>.disabled>a,.navbar-toolbar.navbar-inverse .navbar-nav .open .dropdown-menu>.disabled>a:focus,.navbar-toolbar.navbar-inverse .navbar-nav .open .dropdown-menu>.disabled>a:hover{color:#777;background-color:transparent}}.navbar-toolbar.navbar-inverse .navbar-link{color:#fff}.navbar-toolbar.navbar-inverse .navbar-link:hover{color:#fff}.navbar-toolbar.navbar-inverse .btn-link{color:#fff}.navbar-toolbar.navbar-inverse .btn-link:focus,.navbar-toolbar.navbar-inverse .btn-link:hover{color:#fff}.navbar-toolbar.navbar-inverse .btn-link[disabled]:focus,.navbar-toolbar.navbar-inverse .btn-link[disabled]:hover,fieldset[disabled] .navbar-toolbar.navbar-inverse .btn-link:focus,fieldset[disabled] .navbar-toolbar.navbar-inverse .btn-link:hover{color:#777}.navbar-static-top{border-radius:0}.navbar-fixed-top,.navbar-static-top{border-width:1px 0}.navbar-fixed-bottom{border-width:1px 0}.navbar-fixed-bottom,.navbar-fixed-top{position:fixed;right:0;left:0;border-radius:0}.navbar-fixed-bottom,.navbar-fixed-top{border-radius:0}.navbar-fixed-top{top:0}.navbar-fixed-bottom{bottom:0;margin-bottom:0}.navbar-btn{padding:3px 8px;margin-top:1px}.btn.navbar-masthead-btn{margin-top:7px}.btn.navbar-toolbar-btn{margin-top:3px}.navbar-link{color:#999}.navbar-link:hover{color:#fff}.navbar-inverse .navbar-link{color:#999}.navbar-inverse .navbar-link:hover{color:#333}.navbar-form .checkbox-inline,.navbar-form .radio-inline{color:#999}.breadcrumb{padding:13px 15px;margin-bottom:18px;background-color:#f3f3f3;border-radius:2px}.breadcrumb>li+li{position:relative;display:inline-block;margin-left:20px}.breadcrumb>li+li:before{border-radius:5px}.breadcrumb>li+li:after,.breadcrumb>li+li:before{position:absolute;width:0;height:0;content:""}.breadcrumb>li+li:before{border:7px solid transparent}.breadcrumb>li+li:after{border:5px solid transparent}.breadcrumb>li+li:after,.breadcrumb>li+li:before{top:9px;left:100%}.breadcrumb>li+li:before{margin-top:-7px;border-left:7px solid;border-left-color:#777}.breadcrumb>li+li:after{margin-top:-5px;border-left:5px solid #f3f3f3}.breadcrumb>li+li:after,.breadcrumb>li+li:before{left:-16px}.breadcrumb>li+li:before{color:#999;content:""}.breadcrumb>li>a{color:#999}.breadcrumb>li>a:hover{color:#000}.breadcrumb>.active,.breadcrumb>.active>a{color:#000}.breadcrumb-inverse{background-color:#393832}.breadcrumb-inverse>li+li{position:relative;display:inline-block}.breadcrumb-inverse>li+li:before{border-radius:5px}.breadcrumb-inverse>li+li:after,.breadcrumb-inverse>li+li:before{position:absolute;width:0;height:0;content:""}.breadcrumb-inverse>li+li:before{border:7px solid transparent}.breadcrumb-inverse>li+li:after{border:5px solid transparent}.breadcrumb-inverse>li+li:after,.breadcrumb-inverse>li+li:before{top:9px;left:100%}.breadcrumb-inverse>li+li:before{margin-top:-7px;border-left:7px solid;border-left-color:#666}.breadcrumb-inverse>li+li:after{margin-top:-5px;border-left:5px solid #393832}.breadcrumb-inverse>li+li:after,.breadcrumb-inverse>li+li:before{left:-16px}.breadcrumb-inverse>li>a{color:#999}.breadcrumb-inverse>li>a:hover{color:#fff}.breadcrumb-inverse>.active,.breadcrumb-inverse>.active>a{color:#fff}.breadcrumb-sm{padding:4px 15px;background-color:#fff;border-bottom:1px solid #ebebeb}.breadcrumb-sm.breadcrumb-inverse{background-color:#393832}.pagination{margin:18px 0;border-radius:2px}.pagination>li>a,.pagination>li>span{padding:5px 12px;line-height:1.4;color:#333;background-color:#f3f3f3;border:1px solid #dcdcdc}.pagination>li:first-child>a,.pagination>li:first-child>span{margin-left:0;border-top-left-radius:2px;border-bottom-left-radius:2px}.pagination>li:last-child>a,.pagination>li:last-child>span{border-top-right-radius:2px;border-bottom-right-radius:2px}.pagination>li>a:focus,.pagination>li>a:hover,.pagination>li>span:focus,.pagination>li>span:hover{color:#333;background-color:#f5f5f5;border-color:#c6c6c6;-webkit-box-shadow:0 1px 1px rgba(0,0,0,.1);box-shadow:0 1px 1px rgba(0,0,0,.1)}.pagination>li>a:active{background-color:#f4f4f4;background-image:-webkit-linear-gradient(top,#f6f6f6 0,#f1f1f1 100%);background-image:-o-linear-gradient(top,#f6f6f6 0,#f1f1f1 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#f6f6f6),to(#f1f1f1));background-image:linear-gradient(to bottom,#f6f6f6 0,#f1f1f1 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff6f6f6',endColorstr='#fff1f1f1',GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);background-repeat:repeat-x;-webkit-box-shadow:inset 0 1px 2px rgba(0,0,0,.1);box-shadow:inset 0 1px 2px rgba(0,0,0,.1)}.pagination>.active>a,.pagination>.active>a:focus,.pagination>.active>a:hover,.pagination>.active>span,.pagination>.active>span:focus,.pagination>.active>span:hover{color:#4d90fe;background-color:#f5f5f5;border-color:#c6c6c6;-webkit-box-shadow:none;box-shadow:none}.pagination>.disabled>a,.pagination>.disabled>a:focus,.pagination>.disabled>a:hover,.pagination>.disabled>span,.pagination>.disabled>span:focus,.pagination>.disabled>span:hover{color:#b3b3b3;text-shadow:none;background-color:#f3f3f3;background-image:-webkit-linear-gradient(top,#f5f5f5 0,#f1f1f1 100%);background-image:-o-linear-gradient(top,#f5f5f5 0,#f1f1f1 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#f5f5f5),to(#f1f1f1));background-image:linear-gradient(to bottom,#f5f5f5 0,#f1f1f1 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff5f5f5',endColorstr='#fff1f1f1',GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);background-repeat:repeat-x;border-color:#d9d9d9;-webkit-box-shadow:none;box-shadow:none}.pagination-lg>li>a,.pagination-lg>li>span{padding:9px 14px;font-size:14px;line-height:1.3}.pagination-lg>li:first-child>a,.pagination-lg>li:first-child>span{border-top-left-radius:1px;border-bottom-left-radius:1px}.pagination-lg>li:last-child>a,.pagination-lg>li:last-child>span{border-top-right-radius:1px;border-bottom-right-radius:1px}.pagination-sm>li>a,.pagination-sm>li>span{padding:3px 8px;font-size:12px;line-height:1.5}.pagination-sm>li:first-child>a,.pagination-sm>li:first-child>span{border-top-left-radius:1px;border-bottom-left-radius:1px}.pagination-sm>li:last-child>a,.pagination-sm>li:last-child>span{border-top-right-radius:1px;border-bottom-right-radius:1px}.pager{margin:18px 0}.pager li>a,.pager li>span{padding:11px 24px;overflow:visible;font-size:14px;color:#777;text-decoration:none;white-space:nowrap;cursor:default;background-color:#fff;filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);border:1px solid #5b5b5b;border:1px solid rgba(0,0,0,.1);border-radius:2px;outline:0;-webkit-box-shadow:0 2px 1px rgba(0,0,0,.1),0 0 1px rgba(0,0,0,.1);box-shadow:0 2px 1px rgba(0,0,0,.1),0 0 1px rgba(0,0,0,.1)}.pager li>a:focus,.pager li>a:hover{color:#444;background-color:#fff}.pager li>a:active{color:#444;background-color:#fff}.pager li .icon-prev{position:relative;display:inline-block;padding-right:8px}.pager li .icon-prev:before{border-radius:5px}.pager li .icon-prev:after,.pager li .icon-prev:before{position:absolute;width:0;height:0;content:""}.pager li .icon-prev:before{border:7px solid transparent}.pager li .icon-prev:after{border:4px solid transparent}.pager li .icon-prev:after,.pager li .icon-prev:before{top:-5px;right:100%}.pager li .icon-prev:before{margin-top:-7px;border-right:7px solid;border-right-color:inherit}.pager li .icon-prev:after{margin-top:-4px;border-right:4px solid #fff}.pager li .icon-next{position:relative;display:inline-block;padding-left:8px}.pager li .icon-next:before{border-radius:5px}.pager li .icon-next:after,.pager li .icon-next:before{position:absolute;width:0;height:0;content:""}.pager li .icon-next:before{border:7px solid transparent}.pager li .icon-next:after{border:4px solid transparent}.pager li .icon-next:after,.pager li .icon-next:before{top:-5px;left:100%}.pager li .icon-next:before{margin-top:-7px;border-left:7px solid;border-left-color:inherit}.pager li .icon-next:after{margin-top:-4px;border-left:4px solid #fff}.pager .disabled>a,.pager .disabled>a:focus,.pager .disabled>a:hover,.pager .disabled>span{color:#b3b3b3;background-color:#fafafa;filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);border-color:#d9d9d9;-webkit-box-shadow:none;box-shadow:none}.pager .disabled .icon-prev{position:relative;display:inline-block;padding-right:8px}.pager .disabled .icon-prev:before{border-radius:5px}.pager .disabled .icon-prev:after,.pager .disabled .icon-prev:before{position:absolute;width:0;height:0;content:""}.pager .disabled .icon-prev:before{border:7px solid transparent}.pager .disabled .icon-prev:after{border:4px solid transparent}.pager .disabled .icon-prev:after,.pager .disabled .icon-prev:before{top:-5px;right:100%}.pager .disabled .icon-prev:before{margin-top:-7px;border-right:7px solid;border-right-color:#b3b3b3}.pager .disabled .icon-prev:after{margin-top:-4px;border-right:4px solid #fafafa}.pager .disabled .icon-next{position:relative;display:inline-block;padding-left:8px}.pager .disabled .icon-next:before{border-radius:5px}.pager .disabled .icon-next:after,.pager .disabled .icon-next:before{position:absolute;width:0;height:0;content:""}.pager .disabled .icon-next:before{border:7px solid transparent}.pager .disabled .icon-next:after{border:4px solid transparent}.pager .disabled .icon-next:after,.pager .disabled .icon-next:before{top:-5px;left:100%}.pager .disabled .icon-next:before{margin-top:-7px;border-left:7px solid;border-left-color:#b3b3b3}.pager .disabled .icon-next:after{margin-top:-4px;border-left:4px solid #fafafa}.label{font-size:80%;border-radius:0}.label-default{background-color:#999}.label-default[href]:focus,.label-default[href]:hover{background-color:grey}.label-primary{background-color:#4d90fe}.label-primary[href]:focus,.label-primary[href]:hover{background-color:#1a70fe}.label-success{background-color:#35aa47}.label-success[href]:focus,.label-success[href]:hover{background-color:#298337}.label-info{background-color:#5bc0de}.label-info[href]:focus,.label-info[href]:hover{background-color:#31b0d5}.label-warning{background-color:#faa937}.label-warning[href]:focus,.label-warning[href]:hover{background-color:#f89306}.label-danger{background-color:#d84a38}.label-danger[href]:focus,.label-danger[href]:hover{background-color:#b93524}.badge{font-size:12px}.btn-group-xs>.btn .badge,.btn-xs .badge{font-size:11px}.list-group-item.active>.badge,li.list-group-item.active a>.badge{color:#fff;background-color:#dd4b39}.nav-pills>.active>a>.badge{color:#15c;background-color:#fff}.jumbotron{color:inherit;background-color:#eee}.jumbotron .h1,.jumbotron h1{color:inherit}.jumbotron p{font-size:20px}.container .jumbotron,.container-fluid .jumbotron{border-radius:1px}@media screen and (min-width:768px){.jumbotron .h1,.jumbotron h1{font-size:59px}}.thumbnail{display:block;padding:0;margin-bottom:18px;line-height:1.4;background-color:#fff;border:1px solid #fff;border-radius:0}a.thumbnail.active,a.thumbnail:focus,a.thumbnail:hover{border-color:#fff;-webkit-box-shadow:0 0 0 1px #dedede;box-shadow:0 0 0 1px #dedede}.thumbnail .caption{padding:9px 4px;color:#000}.alert{padding:8px;margin-bottom:18px;border-radius:2px}.alert .alert-link{font-weight:700}.alert-dismissable,.alert-dismissible{padding-right:28px}.alert-success{color:#3c763d;background-color:#dff0d8;border-color:#a3d48e}.alert-success hr{border-top-color:#93cd7c}.alert-success .alert-link{color:#2b542c}.alert-info{color:#31708f;background-color:#d9edf7;border-color:#85c5e5}.alert-info hr{border-top-color:#70bbe1}.alert-info .alert-link{color:#245269}.alert-warning{color:#333;background-color:#f9edbe;border-color:#f0c36d}.alert-warning hr{border-top-color:#eeb956}.alert-warning .alert-link{color:#1a1a1a}.alert-danger{color:#a94442;background-color:#f2dede;border-color:#d59595}.alert-danger hr{border-top-color:#ce8383}.alert-danger .alert-link{color:#843534}.alert-danger,.alert-info,.alert-success,.alert-warning{text-shadow:0 1px 0 rgba(255,255,255,.5)}.progress{height:14px;height:18px;padding:1px;margin-bottom:18px;font-size:12px;background-color:transparent;background-image:none;border:1px solid #999;border-radius:0;-webkit-box-shadow:none;box-shadow:none}.progress-bar{line-height:1.25;background-color:#6188f5;background-image:none;-webkit-box-shadow:none;box-shadow:none}.progress-bar-striped,.progress-striped .progress-bar{background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:-o-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);-webkit-background-size:40px 40px;background-size:40px 40px}.progress-bar-success{background-color:#2f973f}.progress-striped .progress-bar-success{background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:-o-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent)}.progress-bar-info{background-color:#53bddc}.progress-striped .progress-bar-info{background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:-o-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent)}.progress-bar-warning{background-color:#fbb450}.progress-striped .progress-bar-warning{background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:-o-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent)}.progress-bar-danger{background-color:#c13e2c}.progress-striped .progress-bar-danger{background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:-o-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent)}.list-group-item{color:#222;background-color:#fff;border:1px solid #e5e5e5}.list-group-item:first-child{border-top-left-radius:0;border-top-right-radius:0}.list-group-item:last-child{border-bottom-right-radius:0;border-bottom-left-radius:0}.list-group-item .dropdown{display:none}.list-group-item .dropdown-toggle{display:inline-block;padding:5px 6px 5px 5px;color:#222}a.list-group-item,button.list-group-item{color:#555}a.list-group-item .list-group-item-heading,button.list-group-item .list-group-item-heading{color:#333}.list-group-item.active,.list-group-item.active:focus,.list-group-item.active:hover{font-weight:700;color:#dd4b39;background-color:transparent;border-color:#e5e5e5;border-left:4px solid #dd4b39;border-left-color:#dd4b39}.list-group-item.active .list-group-item-text,.list-group-item.active:focus .list-group-item-text,.list-group-item.active:hover .list-group-item-text{font-weight:400;color:#888}.list-group-item.active:focus,.list-group-item.active:hover{background-color:#eee}a.list-group-item:focus,a.list-group-item:hover,li.list-group-item a:focus,li.list-group-item a:hover{color:#555;text-decoration:none;background-color:#eee}li.list-group-item{padding:0;margin-bottom:0;border:0 none}li.list-group-item>a{display:block;padding:5px 17px;margin:0 0 0 14px;color:#222}li.list-group-item.active,li.list-group-item.active:focus,li.list-group-item.active:hover{background-color:transparent}li.list-group-item.active:focus>a,li.list-group-item.active:hover>a,li.list-group-item.active>a{margin-left:10px;color:#dd4b39}.list-group-item-success{color:#3c763d;background-color:#dff0d8}a.list-group-item-success,button.list-group-item-success{color:#3c763d}a.list-group-item-success .list-group-item-heading,button.list-group-item-success .list-group-item-heading{color:inherit}a.list-group-item-success:focus,a.list-group-item-success:hover,button.list-group-item-success:focus,button.list-group-item-success:hover{color:#3c763d;background-color:#d0e9c6}a.list-group-item-success.active,a.list-group-item-success.active:focus,a.list-group-item-success.active:hover,button.list-group-item-success.active,button.list-group-item-success.active:focus,button.list-group-item-success.active:hover{color:#fff;background-color:#3c763d;border-color:#3c763d}.list-group-item-info{color:#31708f;background-color:#d9edf7}a.list-group-item-info,button.list-group-item-info{color:#31708f}a.list-group-item-info .list-group-item-heading,button.list-group-item-info .list-group-item-heading{color:inherit}a.list-group-item-info:focus,a.list-group-item-info:hover,button.list-group-item-info:focus,button.list-group-item-info:hover{color:#31708f;background-color:#c4e3f3}a.list-group-item-info.active,a.list-group-item-info.active:focus,a.list-group-item-info.active:hover,button.list-group-item-info.active,button.list-group-item-info.active:focus,button.list-group-item-info.active:hover{color:#fff;background-color:#31708f;border-color:#31708f}.list-group-item-warning{color:#333;background-color:#f9edbe}a.list-group-item-warning,button.list-group-item-warning{color:#333}a.list-group-item-warning .list-group-item-heading,button.list-group-item-warning .list-group-item-heading{color:inherit}a.list-group-item-warning:focus,a.list-group-item-warning:hover,button.list-group-item-warning:focus,button.list-group-item-warning:hover{color:#333;background-color:#f7e7a7}a.list-group-item-warning.active,a.list-group-item-warning.active:focus,a.list-group-item-warning.active:hover,button.list-group-item-warning.active,button.list-group-item-warning.active:focus,button.list-group-item-warning.active:hover{color:#fff;background-color:#333;border-color:#333}.list-group-item-danger{color:#a94442;background-color:#f2dede}a.list-group-item-danger,button.list-group-item-danger{color:#a94442}a.list-group-item-danger .list-group-item-heading,button.list-group-item-danger .list-group-item-heading{color:inherit}a.list-group-item-danger:focus,a.list-group-item-danger:hover,button.list-group-item-danger:focus,button.list-group-item-danger:hover{color:#a94442;background-color:#ebcccc}a.list-group-item-danger.active,a.list-group-item-danger.active:focus,a.list-group-item-danger.active:hover,button.list-group-item-danger.active,button.list-group-item-danger.active:focus,button.list-group-item-danger.active:hover{color:#fff;background-color:#a94442;border-color:#a94442}.list-group-item-wrapper{margin-left:14px}.list-group-item-wrapper:hover>.dropdown{display:block}.list-group-item-wrapper>a{display:block;padding:5px 17px;margin:0;color:#222}.list-group-item-wrapper>.dropdown:hover+a{background-color:#eee}.list-group-item-wrapper>.dropdown.open{display:block}.list-group-item-wrapper>.dropdown.open+a{background-color:#eee}.list-group-item-wrapper>.dropdown>.dropdown-menu{margin-top:0}.list-group-header{display:block;padding:10px 30px 10px 15px;font-size:11px;font-weight:700;line-height:1.4;color:#999;text-shadow:0 1px 0 rgba(255,255,255,.5);text-transform:uppercase}li.list-group-header{padding:3px 15px}.list-group .list-group-header{margin-top:9px}.list-group-item-menu{padding:0;margin:0;border:0 none;border-radius:0;-webkit-box-shadow:none;box-shadow:none}.list-group-item-menu .list-group-item-wrapper>a{padding-left:30px}.list-group-item-menu .list-group-item-menu .list-group-item-wrapper>a{padding-left:44px}.list-group-item-menu>.list-group-item .collapse-caret{margin-left:28px}.collapse-caret{position:absolute;z-index:1;display:inline-block;width:17px;height:28px;margin-left:14px}.collapse-caret:before{position:absolute;top:12px;left:5px;margin-left:0;content:'';border-bottom:0 dotted}.collapse-caret:hover{background-color:#eee}.collapse-caret.collapsed:before{top:10px;left:6px}.list-group .divider{height:1px;margin:8px 0;margin-right:15px;margin-left:15px;overflow:hidden;background-color:#e5e5e5}.panel{word-wrap:break-word;background-color:#fff;border:1px solid transparent;border-bottom-width:2px;border-radius:3px;-webkit-box-shadow:none;box-shadow:none}.panel-body{padding:15px 20px}.panel-heading{padding:15px 20px;border-top-left-radius:3px;border-top-right-radius:3px}.panel-title{font-size:16px}.panel-footer{padding:15px 20px;background-color:#f8f8f8;border-top:1px solid #e5e5e5;border-bottom-right-radius:3px;border-bottom-left-radius:3px}.panel>.list-group{padding:15px 20px;padding-top:0}.panel>.list-group:first-child .list-group-item:first-child{border-top-left-radius:2px;border-top-right-radius:2px}.panel>.list-group:last-child .list-group-item:last-child{border-bottom-right-radius:2px;border-bottom-left-radius:2px}.panel>.panel-collapse>.table caption,.panel>.table caption,.panel>.table-responsive>.table caption{padding-right:15px 20px;padding-left:15px 20px}.panel>.table-responsive:first-child>.table:first-child,.panel>.table:first-child{border-top-left-radius:2px;border-top-right-radius:2px}.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child,.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child,.panel>.table:first-child>tbody:first-child>tr:first-child,.panel>.table:first-child>thead:first-child>tr:first-child{border-top-left-radius:2px;border-top-right-radius:2px}.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child td:first-child,.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child th:first-child,.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child td:first-child,.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child th:first-child,.panel>.table:first-child>tbody:first-child>tr:first-child td:first-child,.panel>.table:first-child>tbody:first-child>tr:first-child th:first-child,.panel>.table:first-child>thead:first-child>tr:first-child td:first-child,.panel>.table:first-child>thead:first-child>tr:first-child th:first-child{border-top-left-radius:2px}.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child td:last-child,.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child th:last-child,.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child td:last-child,.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child th:last-child,.panel>.table:first-child>tbody:first-child>tr:first-child td:last-child,.panel>.table:first-child>tbody:first-child>tr:first-child th:last-child,.panel>.table:first-child>thead:first-child>tr:first-child td:last-child,.panel>.table:first-child>thead:first-child>tr:first-child th:last-child{border-top-right-radius:2px}.panel>.table-responsive:last-child>.table:last-child,.panel>.table:last-child{border-bottom-right-radius:2px;border-bottom-left-radius:2px}.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child,.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child,.panel>.table:last-child>tbody:last-child>tr:last-child,.panel>.table:last-child>tfoot:last-child>tr:last-child{border-bottom-right-radius:2px;border-bottom-left-radius:2px}.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child td:first-child,.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child th:first-child,.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child td:first-child,.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child th:first-child,.panel>.table:last-child>tbody:last-child>tr:last-child td:first-child,.panel>.table:last-child>tbody:last-child>tr:last-child th:first-child,.panel>.table:last-child>tfoot:last-child>tr:last-child td:first-child,.panel>.table:last-child>tfoot:last-child>tr:last-child th:first-child{border-bottom-left-radius:2px}.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child td:last-child,.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child th:last-child,.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child td:last-child,.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child th:last-child,.panel>.table:last-child>tbody:last-child>tr:last-child td:last-child,.panel>.table:last-child>tbody:last-child>tr:last-child th:last-child,.panel>.table:last-child>tfoot:last-child>tr:last-child td:last-child,.panel>.table:last-child>tfoot:last-child>tr:last-child th:last-child{border-bottom-right-radius:2px}.panel>.panel-body+.table,.panel>.panel-body+.table-responsive,.panel>.table+.panel-body,.panel>.table-responsive+.panel-body{border-top:1px solid #ddd}.panel-default{border-color:#d8d8d8}.panel-default>.panel-heading{color:#333;background-color:#fff;border-color:#fff}.panel-default>.panel-heading+.panel-collapse>.panel-body{border-top-color:#d8d8d8}.panel-default>.panel-heading .badge{color:#fff;background-color:#333}.panel-default>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#d8d8d8}.panel-primary{border-color:#4d90fe}.panel-primary>.panel-heading{color:#fff;background-color:#4d90fe;border-color:#4d90fe}.panel-primary>.panel-heading+.panel-collapse>.panel-body{border-top-color:#4d90fe}.panel-primary>.panel-heading .badge{color:#4d90fe;background-color:#fff}.panel-primary>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#4d90fe}.panel-success{border-color:#a3d48e}.panel-success>.panel-heading{color:#3c763d;background-color:#dff0d8;border-color:#a3d48e}.panel-success>.panel-heading+.panel-collapse>.panel-body{border-top-color:#a3d48e}.panel-success>.panel-heading .badge{color:#dff0d8;background-color:#3c763d}.panel-success>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#a3d48e}.panel-info{border-color:#85c5e5}.panel-info>.panel-heading{color:#31708f;background-color:#d9edf7;border-color:#85c5e5}.panel-info>.panel-heading+.panel-collapse>.panel-body{border-top-color:#85c5e5}.panel-info>.panel-heading .badge{color:#d9edf7;background-color:#31708f}.panel-info>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#85c5e5}.panel-warning{border-color:#f0c36d}.panel-warning>.panel-heading{color:#333;background-color:#f9edbe;border-color:#f0c36d}.panel-warning>.panel-heading+.panel-collapse>.panel-body{border-top-color:#f0c36d}.panel-warning>.panel-heading .badge{color:#f9edbe;background-color:#333}.panel-warning>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#f0c36d}.panel-danger{border-color:#d59595}.panel-danger>.panel-heading{color:#a94442;background-color:#f2dede;border-color:#d59595}.panel-danger>.panel-heading+.panel-collapse>.panel-body{border-top-color:#d59595}.panel-danger>.panel-heading .badge{color:#f2dede;background-color:#a94442}.panel-danger>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#d59595}.panel-group{margin-bottom:18px}.panel-group .panel{border-color:transparent;border-radius:0}.panel-group .panel+.panel{margin-top:-3px}.panel-group .panel-heading{padding:0 15px;background-color:#fafafa;border-top:1px dashed #ccc;border-bottom:1px dashed #ccc}.panel-group .panel-heading a{display:block;padding:10px 0 9px;color:#444;text-decoration:none}.panel-group .panel-heading a:before{margin-right:7px;content:"\e082"}.panel-group .panel-heading a:hover{background-color:#f5f5f5}.panel-group .panel-heading a:focus{outline:0}.panel-group .panel-heading a.collapsed:before{margin-right:7px;content:"\e081"}.panel-group .panel-heading .panel-title{font-size:13px}.panel-group .panel-heading+.panel-collapse>.list-group,.panel-group .panel-heading+.panel-collapse>.panel-body{border-top:0 none}.panel-group .panel-footer+.panel-collapse .panel-body{border-bottom:0 none}.well{background-color:#f1f1f1;border:1px solid #e5e5e5;border-radius:0;-webkit-box-shadow:none;box-shadow:none}.well-lg{border-radius:0}.well-sm{border-radius:0}.scrollable::-webkit-scrollbar{width:10px;height:16px}.scrollable::-webkit-scrollbar:hover{background-color:#f3f3f3;border:1px solid #dbdbdb}.scrollable::-webkit-scrollbar-button:end:increment,.scrollable::-webkit-scrollbar-button:start:decrement{display:block;height:0;background-color:transparent}.scrollable::-webkit-scrollbar-track{-webkit-background-clip:padding-box;background-clip:padding-box;border:solid transparent;border-width:0 0 0 4px}.scrollable::-webkit-scrollbar-track-piece{background-color:transparent;border-radius:0}.scrollable::-webkit-scrollbar-thumb{background-color:#515151;background-color:rgba(0,0,0,.2);-webkit-background-clip:padding-box;background-clip:padding-box;border:solid transparent;border-width:0;-webkit-box-shadow:inset 1px 1px 0 rgba(0,0,0,.1),inset 0 -1px 0 rgba(0,0,0,.07);box-shadow:inset 1px 1px 0 rgba(0,0,0,.1),inset 0 -1px 0 rgba(0,0,0,.07)}.scrollable::-webkit-scrollbar-thumb:hover{background-color:#949494}.scrollable::-webkit-scrollbar-thumb:active{background-color:#3b3b3b;background-color:rgba(0,0,0,.5);-webkit-box-shadow:inset 1px 1px 3px rgba(0,0,0,.35);box-shadow:inset 1px 1px 3px rgba(0,0,0,.35)}.scrollable::-webkit-scrollbar-thumb:horizontal,.scrollable::-webkit-scrollbar-thumb:vertical{background-color:#c6c6c6;border-radius:0}.modal-content{color:#222;border:1px solid #aaa;border:1px solid rgba(0,0,0,.333);border-radius:0;-webkit-box-shadow:0 4px 16px rgba(0,0,0,.2);box-shadow:0 4px 16px rgba(0,0,0,.2)}.modal-backdrop{background-color:#fff}.modal-header .close{font-weight:400;filter:alpha(opacity=40);opacity:.4}.modal-body{padding:15px}.tooltip{font-family:Arial,Helvetica,sans-serif;font-size:11px;font-style:normal;font-weight:400;font-weight:700;line-height:1.4;line-height:1.25;text-align:left;text-align:start;text-decoration:none;text-shadow:none;text-transform:none;letter-spacing:normal;word-break:normal;word-break:break-word;word-spacing:normal;word-wrap:normal;white-space:normal;line-break:auto}.tooltip.in{filter:alpha(opacity=100);opacity:1}.tooltip-inner{padding:7px 9px;background-color:#2a2a2a;border:1px solid #fff;border-radius:0}.tooltip-arrow:before{position:absolute;z-index:-1;content:" ";border:7px solid transparent}.tooltip.top .tooltip-arrow,.tooltip.top-left .tooltip-arrow,.tooltip.top-right .tooltip-arrow{bottom:1px;border-top-color:#2a2a2a}.tooltip.top .tooltip-arrow:before,.tooltip.top-left .tooltip-arrow:before,.tooltip.top-right .tooltip-arrow:before{top:-5px;left:-7px;border-top-color:#fff;border-bottom:0 dotted}.tooltip.right .tooltip-arrow{left:1px;border-right-color:#2a2a2a}.tooltip.right .tooltip-arrow:before{top:-7px;right:-5px;border-right-color:#fff;border-left:0 dotted}.tooltip.left .tooltip-arrow{right:1px;border-left-color:#2a2a2a}.tooltip.left .tooltip-arrow:before{top:-7px;left:-5px;border-right:0 dotted;border-left-color:#fff}.tooltip.bottom .tooltip-arrow,.tooltip.bottom-left .tooltip-arrow,.tooltip.bottom-right .tooltip-arrow{top:1px;border-bottom-color:#2a2a2a}.tooltip.bottom .tooltip-arrow:before,.tooltip.bottom-left .tooltip-arrow:before,.tooltip.bottom-right .tooltip-arrow:before{bottom:-5px;left:-7px;border-top:0 dotted;border-bottom-color:#fff}.popover{padding:0;font-family:Arial,Helvetica,sans-serif;font-size:13px;font-style:normal;font-weight:400;line-height:1.4;text-align:left;text-align:start;text-decoration:none;text-shadow:none;text-transform:none;letter-spacing:normal;word-break:normal;word-spacing:normal;word-wrap:normal;white-space:normal;background-color:#fff;border-radius:2px;-webkit-box-shadow:0 2px 10px rgba(0,0,0,.2);box-shadow:0 2px 10px rgba(0,0,0,.2);line-break:auto}.popover-footer,.popover-title{padding:10px;font-size:13px;background-color:#f5f5f5;border-bottom:1px solid #ccc;border-bottom:1px solid rgba(0,0,0,.2);border-radius:0}.popover-footer{border-top:1px solid #ccc;border-top:1px solid rgba(0,0,0,.2);border-bottom:none}.popover-content{padding:10px}.carousel{width:100%;padding:50px;overflow:hidden;background-color:#f5f5f5;background-image:-webkit-linear-gradient(top,#eee 0,#f5f5f5 100%),-webkit-linear-gradient(bottom,#eee 0,#f5f5f5 100%);background-image:-o-linear-gradient(top,#eee 0,#f5f5f5 100%),-o-linear-gradient(bottom,#eee 0,#f5f5f5 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#eee),to(#f5f5f5)),-webkit-gradient(linear,left bottom,left top,from(#eee),to(#f5f5f5));background-image:linear-gradient(to bottom,#eee 0,#f5f5f5 100%),linear-gradient(to top,#eee 0,#f5f5f5 100%);background-repeat:no-repeat;background-position:0 0,0 100%;-webkit-background-size:100% 10px;background-size:100% 10px}.carousel-control{width:100px;color:#777;text-shadow:none;filter:alpha(opacity=33);opacity:.33}.carousel-control.left{background-image:none}.carousel-control.right{background-image:none}.carousel-control:focus,.carousel-control:hover{color:#777}.carousel-control .icon-next:before,.carousel-control .icon-prev:before{content:''}.carousel-control .icon-prev{position:relative;position:absolute;right:0;display:inline-block}.carousel-control .icon-prev:before{border-radius:20px}.carousel-control .icon-prev:after,.carousel-control .icon-prev:before{position:absolute;width:0;height:0;content:""}.carousel-control .icon-prev:before{border:22px solid transparent}.carousel-control .icon-prev:after{border:19px solid transparent}.carousel-control .icon-prev:after,.carousel-control .icon-prev:before{top:8px;right:100%}.carousel-control .icon-prev:before{margin-top:-22px;border-right:22px solid;border-right-color:#777}.carousel-control .icon-prev:after{margin-top:-19px;border-right:19px solid #f5f5f5}.carousel-control .icon-next{position:relative;position:absolute;right:0;left:50%;display:inline-block}.carousel-control .icon-next:before{border-radius:20px}.carousel-control .icon-next:after,.carousel-control .icon-next:before{position:absolute;width:0;height:0;content:""}.carousel-control .icon-next:before{border:22px solid transparent}.carousel-control .icon-next:after{border:19px solid transparent}.carousel-control .icon-next:after,.carousel-control .icon-next:before{top:8px;left:100%}.carousel-control .icon-next:before{margin-top:-22px;border-left:22px solid;border-left-color:#777}.carousel-control .icon-next:after{margin-top:-19px;border-left:19px solid #f5f5f5}.carousel-control .icon-next:after,.carousel-control .icon-next:before{left:50%}.carousel-indicators{bottom:5px;left:0;width:100%;margin-left:0}.carousel-indicators li{background-color:#c2c2c2;border:1px solid #c2c2c2}.carousel-indicators .active{width:10px;height:10px;margin:1px;background-color:#444;border:1px solid #444}.carousel-caption{right:0;bottom:0;left:0;padding:10px;color:#fff;text-shadow:none;background-color:#262626;background-color:rgba(0,0,0,.55)}body{padding-top:40px;padding-bottom:40px;background-color:#fff}.form-signin{max-width:330px;padding:15px;margin:0 auto}.form-signin-heading{margin:0 0 15px;font-size:18px;font-weight:400;color:#555}.form-signin .checkbox{margin-bottom:10px;font-weight:normal}.form-signin .form-control{position:relative;height:auto;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;padding:10px;font-size:16px}.form-signin .form-control:focus{z-index:2}.form-signin input[type="email"]{margin-bottom:10px}.form-signin input[type="password"]{margin-bottom:10px}.card{width:304px;padding:20px 25px 30px;margin:0 auto 25px;background-color:#f7f7f7;border-radius:2px;-webkit-box-shadow:0 2px 2px rgba(0,0,0,.3);box-shadow:0 2px 2px rgba(0,0,0,.3)}.card-signin{width:354px;padding:40px}.card-signin .profile-img{display:block;width:96px;height:96px;margin:0 auto 10px} \ No newline at end of file diff --git a/src/DjangoBlog/collectedstatic/CACHE/css/output.758e876fbda7.css b/src/DjangoBlog/collectedstatic/CACHE/css/output.758e876fbda7.css new file mode 100644 index 0000000..42a671f --- /dev/null +++ b/src/DjangoBlog/collectedstatic/CACHE/css/output.758e876fbda7.css @@ -0,0 +1 @@ +html,body,div,span,applet,object,iframe,h1,h2,h3,h4,h5,h6,p,blockquote,pre,a,abbr,acronym,address,big,cite,code,del,dfn,em,img,ins,kbd,q,s,samp,small,strike,strong,sub,sup,tt,var,b,u,i,center,dl,dt,dd,ol,ul,li,fieldset,form,label,legend,table,caption,tbody,tfoot,thead,tr,th,td,article,aside,canvas,details,embed,figure,figcaption,footer,header,hgroup,menu,nav,output,ruby,section,summary,time,mark,audio,video{margin:0;padding:0;border:0;font-size:100%;vertical-align:baseline}body{line-height:1}ol,ul{list-style:none}blockquote,q{quotes:none}blockquote:before,blockquote:after,q:before,q:after{content:'';content:none}table{border-collapse:collapse;border-spacing:0}caption,th,td{font-weight:normal;text-align:left}h1,h2,h3,h4,h5,h6{clear:both}html{overflow-y:scroll;font-size:100%;-webkit-text-size-adjust:100%;-ms-text-size-adjust:100%}a:focus{outline:thin dotted}article,aside,details,figcaption,figure,footer,header,hgroup,nav,section{display:block}audio,canvas,video{display:inline-block}audio:not([controls]){display:none}del{color:#333}ins{background:#fff9c0;text-decoration:none}hr{background-color:#ccc;border:0;height:1px;margin:24px;margin-bottom:1.714285714rem}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sup{top:-0.5em}sub{bottom:-0.25em}small{font-size:smaller}img{border:0;-ms-interpolation-mode:bicubic}.clear:after,.wrapper:after,.format-status .entry-header:after{clear:both}.clear:before,.clear:after,.wrapper:before,.wrapper:after,.format-status .entry-header:before,.format-status .entry-header:after{display:table;content:""}.archive-title,.page-title,.widget-title,.entry-content th,.comment-content th{font-size:11px;font-size:0.785714286rem;line-height:2.181818182;font-weight:bold;text-transform:uppercase;color:#636363}article.format-quote footer.entry-meta,article.format-link footer.entry-meta,article.format-status footer.entry-meta{font-size:11px;font-size:0.785714286rem;line-height:2.181818182}button,input,select,textarea{border:1px solid #ccc;border-radius:3px;font-family:inherit;padding:6px;padding:0.428571429rem}button,input{line-height:normal}textarea{font-size:100%;overflow:auto;vertical-align:top}input[type="checkbox"],input[type="radio"],input[type="file"],input[type="hidden"],input[type="image"],input[type="color"]{border:0;border-radius:0;padding:0}.menu-toggle,input[type="submit"],input[type="button"],input[type="reset"],article.post-password-required input[type=submit],.bypostauthor cite span{padding:6px 10px;padding:0.428571429rem 0.714285714rem;font-size:11px;font-size:0.785714286rem;line-height:1.428571429;font-weight:normal;color:#7c7c7c;background-color:#e6e6e6;background-repeat:repeat-x;background-image:-moz-linear-gradient(top,#f4f4f4,#e6e6e6);background-image:-ms-linear-gradient(top,#f4f4f4,#e6e6e6);background-image:-webkit-linear-gradient(top,#f4f4f4,#e6e6e6);background-image:-o-linear-gradient(top,#f4f4f4,#e6e6e6);background-image:linear-gradient(to bottom,#f4f4f4,#e6e6e6);border:1px solid #d2d2d2;border-radius:3px;box-shadow:0 1px 2px rgba(64,64,64,0.1)}.menu-toggle,button,input[type="submit"],input[type="button"],input[type="reset"]{cursor:pointer}button[disabled],input[disabled]{cursor:default}.menu-toggle:hover,.menu-toggle:focus,button:hover,input[type="submit"]:hover,input[type="button"]:hover,input[type="reset"]:hover,article.post-password-required input[type=submit]:hover{color:#5e5e5e;background-color:#ebebeb;background-repeat:repeat-x;background-image:-moz-linear-gradient(top,#f9f9f9,#ebebeb);background-image:-ms-linear-gradient(top,#f9f9f9,#ebebeb);background-image:-webkit-linear-gradient(top,#f9f9f9,#ebebeb);background-image:-o-linear-gradient(top,#f9f9f9,#ebebeb);background-image:linear-gradient(to bottom,#f9f9f9,#ebebeb)}.menu-toggle:active,.menu-toggle.toggled-on,button:active,input[type="submit"]:active,input[type="button"]:active,input[type="reset"]:active{color:#757575;background-color:#e1e1e1;background-repeat:repeat-x;background-image:-moz-linear-gradient(top,#ebebeb,#e1e1e1);background-image:-ms-linear-gradient(top,#ebebeb,#e1e1e1);background-image:-webkit-linear-gradient(top,#ebebeb,#e1e1e1);background-image:-o-linear-gradient(top,#ebebeb,#e1e1e1);background-image:linear-gradient(to bottom,#ebebeb,#e1e1e1);box-shadow:inset 0 0 8px 2px #c6c6c6,0 1px 0 0 #f4f4f4;border-color:transparent}.bypostauthor cite span{color:#fff;background-color:#21759b;background-image:none;border:1px solid #1f6f93;border-radius:2px;box-shadow:none;padding:0}.entry-content img,.comment-content img,.widget img{max-width:100%}img[class*="align"],img[class*="wp-image-"],img[class*="attachment-"]{height:auto}img.size-full,img.size-large,img.header-image,img.wp-post-image{max-width:100%;height:auto}embed,iframe,object,video{max-width:100%}.entry-content .twitter-tweet-rendered{max-width:100%!important}.alignleft{float:left}.alignright{float:right}.aligncenter{display:block;margin-left:auto;margin-right:auto}.entry-content img,.comment-content img,.widget img,img.header-image,.author-avatar img,img.wp-post-image{border-radius:3px;box-shadow:0 1px 4px rgba(0,0,0,0.2)}.wp-caption{max-width:100%;padding:4px}.wp-caption .wp-caption-text,.gallery-caption,.entry-caption{font-style:italic;font-size:12px;font-size:0.857142857rem;line-height:2;color:#757575}img.wp-smiley,.rsswidget img{border:0;border-radius:0;box-shadow:none;margin-bottom:0;margin-top:0;padding:0}.entry-content dl.gallery-item{margin:0}.gallery-item a,.gallery-caption{width:90%}.gallery-item a{display:block}.gallery-caption a{display:inline}.gallery-columns-1 .gallery-item a{max-width:100%;width:auto}.gallery .gallery-icon img{height:auto;max-width:90%;padding:5%}.gallery-columns-1 .gallery-icon img{padding:3%}.site-content nav{clear:both;line-height:2;overflow:hidden}#nav-above{padding:24px 0;padding:1.714285714rem 0}#nav-above{display:none}.paged #nav-above{display:block}.nav-previous,.previous-image{float:left;width:50%}.nav-next,.next-image{float:right;text-align:right;width:50%}.nav-single + .comments-area,#comment-nav-above{margin:48px 0;margin:3.428571429rem 0}.author .archive-header{margin-bottom:24px;margin-bottom:1.714285714rem}.author-info{border-top:1px solid #ededed;margin:24px 0;margin:1.714285714rem 0;padding-top:24px;padding-top:1.714285714rem;overflow:hidden}.author-description p{color:#757575;font-size:13px;font-size:0.928571429rem;line-height:1.846153846}.author.archive .author-info{border-top:0;margin:0 0 48px;margin:0 0 3.428571429rem}.author.archive .author-avatar{margin-top:0}html{font-size:87.5%}body{font-size:14px;font-size:1rem;font-family:Helvetica,Arial,sans-serif;text-rendering:optimizeLegibility;color:#444}body.custom-font-enabled{font-family:"Open Sans",Helvetica,Arial,sans-serif}a{outline:none;color:#21759b}a:hover{color:#0f3647}.assistive-text,.site .screen-reader-text{position:absolute!important;clip:rect(1px,1px,1px,1px);overflow:hidden;height:1px;width:1px}.main-navigation .assistive-text:focus,.site .screen-reader-text:hover,.site .screen-reader-text:active,.site .screen-reader-text:focus{background:#fff;border:2px solid #333;border-radius:3px;clip:auto!important;color:#000;display:block;font-size:12px;height:auto;padding:12px;position:absolute;top:5px;left:5px;width:auto;z-index:100000}.site{padding:0 24px;padding:0 1.714285714rem;background-color:#fff}.site-content{margin:24px 0 0;margin:1.714285714rem 0 0}.widget-area{margin:24px 0 0;margin:1.714285714rem 0 0}.site-header{padding:24px 0;padding:1.714285714rem 0}.site-header h1,.site-header h2{text-align:center}.site-header h1 a,.site-header h2 a{color:#515151;display:inline-block;text-decoration:none}.site-header h1 a:hover,.site-header h2 a:hover{color:#21759b}.site-header h1{font-size:24px;font-size:1.714285714rem;line-height:1.285714286;margin-bottom:14px;margin-bottom:1rem}.site-header h2{font-weight:normal;font-size:13px;font-size:0.928571429rem;line-height:1.846153846;color:#757575}.header-image{margin-top:24px;margin-top:1.714285714rem}.main-navigation{margin-top:24px;margin-top:1.714285714rem;text-align:center}.main-navigation li{margin-top:24px;margin-top:1.714285714rem;font-size:12px;font-size:0.857142857rem;line-height:1.42857143}.main-navigation a{color:#5e5e5e}.main-navigation a:hover,.main-navigation a:focus{color:#21759b}.main-navigation ul.nav-menu,.main-navigation div.nav-menu>ul{display:none}.main-navigation ul.nav-menu.toggled-on,.menu-toggle{display:inline-block}section[role="banner"]{margin-bottom:48px;margin-bottom:3.428571429rem}.widget-area .widget{-webkit-hyphens:auto;-moz-hyphens:auto;hyphens:auto;margin-bottom:48px;margin-bottom:3.428571429rem;word-wrap:break-word}.widget-area .widget h3{margin-bottom:24px;margin-bottom:1.714285714rem}.widget-area .widget p,.widget-area .widget li,.widget-area .widget .textwidget{font-size:13px;font-size:0.928571429rem;line-height:1.846153846}.widget-area .widget p{margin-bottom:24px;margin-bottom:1.714285714rem}.widget-area .textwidget ul,.widget-area .textwidget ol{list-style:disc outside;margin:0 0 24px;margin:0 0 1.714285714rem}.widget-area .textwidget li>ul,.widget-area .textwidget li>ol{margin-bottom:0}.widget-area .textwidget ol{list-style:decimal}.widget-area .textwidget li{margin-left:36px;margin-left:2.571428571rem}.widget-area .widget a{color:#757575}.widget-area .widget a:hover{color:#21759b}.widget-area .widget a:visited{color:#9f9f9f}.widget-area #s{width:53.66666666666%}footer[role="contentinfo"]{border-top:1px solid #ededed;clear:both;font-size:12px;font-size:0.857142857rem;line-height:2;max-width:960px;max-width:68.571428571rem;margin-top:24px;margin-top:1.714285714rem;margin-left:auto;margin-right:auto;padding:24px 0;padding:1.714285714rem 0}footer[role="contentinfo"] a{color:#686868}footer[role="contentinfo"] a:hover{color:#21759b}.site-info span[role=separator]{padding:0 0.3em 0 0.6em}.site-info span[role=separator]::before{content:'\002f'}.entry-meta{clear:both}.entry-header{margin-bottom:24px;margin-bottom:1.714285714rem}.entry-header img.wp-post-image{margin-bottom:24px;margin-bottom:1.714285714rem}.entry-header .entry-title{font-size:20px;font-size:1.428571429rem;line-height:1.2;font-weight:normal}.entry-header .entry-title a{text-decoration:none}.entry-header .entry-format{margin-top:24px;margin-top:1.714285714rem;font-weight:normal}.entry-header .comments-link{margin-top:24px;margin-top:1.714285714rem;font-size:13px;font-size:0.928571429rem;line-height:1.846153846;color:#757575}.comments-link a,.entry-meta a{color:#757575}.comments-link a:hover,.entry-meta a:hover{color:#21759b}article.sticky .featured-post{border-top:4px double #ededed;border-bottom:4px double #ededed;color:#757575;font-size:13px;font-size:0.928571429rem;line-height:3.692307692;margin-bottom:24px;margin-bottom:1.714285714rem;text-align:center}.entry-content,.entry-summary,.mu_register{line-height:1.714285714}.entry-content h1,.comment-content h1,.entry-content h2,.comment-content h2,.entry-content h3,.comment-content h3,.entry-content h4,.comment-content h4,.entry-content h5,.comment-content h5,.entry-content h6,.comment-content h6{margin:24px 0;margin:1.714285714rem 0;line-height:1.714285714}.entry-content h1,.comment-content h1{font-size:21px;font-size:1.5rem;line-height:1.5}.entry-content h2,.comment-content h2,.mu_register h2{font-size:18px;font-size:1.285714286rem;line-height:1.6}.entry-content h3,.comment-content h3{font-size:16px;font-size:1.142857143rem;line-height:1.846153846}.entry-content h4,.comment-content h4{font-size:14px;font-size:1rem;line-height:1.846153846}.entry-content h5,.comment-content h5{font-size:13px;font-size:0.928571429rem;line-height:1.846153846}.entry-content h6,.comment-content h6{font-size:12px;font-size:0.857142857rem;line-height:1.846153846}.entry-content p,.entry-summary p,.comment-content p,.mu_register p{margin:0 0 24px;margin:0 0 1.714285714rem;line-height:1.714285714}.entry-content a:visited,.comment-content a:visited{color:#9f9f9f}.entry-content .more-link{white-space:nowrap}.entry-content ol,.comment-content ol,.entry-content ul,.comment-content ul,.mu_register ul{margin:0 0 24px;margin:0 0 1.714285714rem;line-height:1.714285714}.entry-content ul ul,.comment-content ul ul,.entry-content ol ol,.comment-content ol ol,.entry-content ul ol,.comment-content ul ol,.entry-content ol ul,.comment-content ol ul{margin-bottom:0}.entry-content ul,.comment-content ul,.mu_register ul{list-style:disc outside}.entry-content ol,.comment-content ol{list-style:decimal outside}.entry-content li,.comment-content li,.mu_register li{margin:0 0 0 36px;margin:0 0 0 2.571428571rem}.entry-content blockquote,.comment-content blockquote{margin-bottom:24px;margin-bottom:1.714285714rem;padding:24px;padding:1.714285714rem;font-style:italic}.entry-content blockquote p:last-child,.comment-content blockquote p:last-child{margin-bottom:0}.entry-content code,.comment-content code{font-family:Consolas,Monaco,Lucida Console,monospace;font-size:12px;font-size:0.857142857rem;line-height:2}.entry-content pre,.comment-content pre{border:1px solid #ededed;color:#666;font-family:Consolas,Monaco,Lucida Console,monospace;font-size:12px;font-size:0.857142857rem;line-height:1.714285714;margin:24px 0;margin:1.714285714rem 0;overflow:auto;padding:24px;padding:1.714285714rem}.entry-content pre code,.comment-content pre code{display:block}.entry-content abbr,.comment-content abbr,.entry-content dfn,.comment-content dfn,.entry-content acronym,.comment-content acronym{border-bottom:1px dotted #666;cursor:help}.entry-content address,.comment-content address{display:block;line-height:1.714285714;margin:0 0 24px;margin:0 0 1.714285714rem}img.alignleft,.wp-caption.alignleft{margin:12px 24px 12px 0;margin:0.857142857rem 1.714285714rem 0.857142857rem 0}img.alignright,.wp-caption.alignright{margin:12px 0 12px 24px;margin:0.857142857rem 0 0.857142857rem 1.714285714rem}img.aligncenter,.wp-caption.aligncenter{clear:both;margin-top:12px;margin-top:0.857142857rem;margin-bottom:12px;margin-bottom:0.857142857rem}.entry-content embed,.entry-content iframe,.entry-content object,.entry-content video{margin-bottom:24px;margin-bottom:1.714285714rem}.entry-content dl,.comment-content dl{margin:0 24px;margin:0 1.714285714rem}.entry-content dt,.comment-content dt{font-weight:bold;line-height:1.714285714}.entry-content dd,.comment-content dd{line-height:1.714285714;margin-bottom:24px;margin-bottom:1.714285714rem}.entry-content table,.comment-content table{border-bottom:1px solid #ededed;color:#757575;font-size:12px;font-size:0.857142857rem;line-height:2;margin:0 0 24px;margin:0 0 1.714285714rem;width:100%}.entry-content table caption,.comment-content table caption{font-size:16px;font-size:1.142857143rem;margin:24px 0;margin:1.714285714rem 0}.entry-content td,.comment-content td{border-top:1px solid #ededed;padding:6px 10px 6px 0}.site-content article{border-bottom:4px double #ededed;margin-bottom:72px;margin-bottom:5.142857143rem;padding-bottom:24px;padding-bottom:1.714285714rem;word-wrap:break-word;-webkit-hyphens:auto;-moz-hyphens:auto;hyphens:auto}.page-links{clear:both;line-height:1.714285714}footer.entry-meta{margin-top:24px;margin-top:1.714285714rem;font-size:13px;font-size:0.928571429rem;line-height:1.846153846;color:#757575}.single-author .entry-meta .by-author{display:none}.mu_register h2{color:#757575;font-weight:normal}.archive-header,.page-header{margin-bottom:48px;margin-bottom:3.428571429rem;padding-bottom:22px;padding-bottom:1.571428571rem;border-bottom:1px solid #ededed}.archive-meta{color:#757575;font-size:12px;font-size:0.857142857rem;line-height:2;margin-top:22px;margin-top:1.571428571rem}.attachment .entry-content .mejs-audio{max-width:400px}.attachment .entry-content .mejs-container{margin-bottom:24px}.article.attachment{overflow:hidden}.image-attachment div.attachment{text-align:center}.image-attachment div.attachment p{text-align:center}.image-attachment div.attachment img{display:block;height:auto;margin:0 auto;max-width:100%}.image-attachment .entry-caption{margin-top:8px;margin-top:0.571428571rem}article.format-aside h1{margin-bottom:24px;margin-bottom:1.714285714rem}article.format-aside h1 a{text-decoration:none;color:#4d525a}article.format-aside h1 a:hover{color:#2e3542}article.format-aside .aside{padding:24px 24px 0;padding:1.714285714rem;background:#d2e0f9;border-left:22px solid #a8bfe8}article.format-aside p{font-size:13px;font-size:0.928571429rem;line-height:1.846153846;color:#4a5466}article.format-aside blockquote:last-child,article.format-aside p:last-child{margin-bottom:0}article.format-image footer h1{font-size:13px;font-size:0.928571429rem;line-height:1.846153846;font-weight:normal}article.format-image footer h2{font-size:11px;font-size:0.785714286rem;line-height:2.181818182}article.format-image footer a h2{font-weight:normal}article.format-link header{padding:0 10px;padding:0 0.714285714rem;float:right;font-size:11px;font-size:0.785714286rem;line-height:2.181818182;font-weight:bold;font-style:italic;text-transform:uppercase;color:#848484;background-color:#ebebeb;border-radius:3px}article.format-link .entry-content{max-width:80%;float:left}article.format-link .entry-content a{font-size:22px;font-size:1.571428571rem;line-height:1.090909091;text-decoration:none}article.format-quote .entry-content p{margin:0;padding-bottom:24px;padding-bottom:1.714285714rem}article.format-quote .entry-content blockquote{display:block;padding:24px 24px 0;padding:1.714285714rem 1.714285714rem 0;font-size:15px;font-size:1.071428571rem;line-height:1.6;font-style:normal;color:#6a6a6a;background:#efefef}.format-status .entry-header{margin-bottom:24px;margin-bottom:1.714285714rem}.format-status .entry-header header{display:inline-block}.format-status .entry-header h1{font-size:15px;font-size:1.071428571rem;font-weight:normal;line-height:1.6;margin:0}.format-status .entry-header h2{font-size:12px;font-size:0.857142857rem;font-weight:normal;line-height:2;margin:0}.format-status .entry-header header a{color:#757575}.format-status .entry-header header a:hover{color:#21759b}.format-status .entry-header img{float:left;margin-right:21px;margin-right:1.5rem}.comments-title{margin-bottom:48px;margin-bottom:3.428571429rem;font-size:16px;font-size:1.142857143rem;line-height:1.5;font-weight:normal}.comments-area article{margin:24px 0;margin:1.714285714rem 0}.comments-area article header{margin:0 0 48px;margin:0 0 3.428571429rem;overflow:hidden;position:relative}.comments-area article header img{float:left;padding:0;line-height:0}.comments-area article header cite,.comments-area article header time{display:block;margin-left:85px;margin-left:6.071428571rem}.comments-area article header cite{font-style:normal;font-size:15px;font-size:1.071428571rem;line-height:1.42857143}.comments-area cite b{font-weight:normal}.comments-area article header time{line-height:1.714285714;text-decoration:none;font-size:12px;font-size:0.857142857rem;color:#5e5e5e}.comments-area article header a{text-decoration:none;color:#5e5e5e}.comments-area article header a:hover{color:#21759b}.comments-area article header cite a{color:#444}.comments-area article header cite a:hover{text-decoration:underline}.comments-area article header h4{position:absolute;top:0;right:0;padding:6px 12px;padding:0.428571429rem 0.857142857rem;font-size:12px;font-size:0.857142857rem;font-weight:normal;color:#fff;background-color:#0088d0;background-repeat:repeat-x;background-image:-moz-linear-gradient(top,#009cee,#0088d0);background-image:-ms-linear-gradient(top,#009cee,#0088d0);background-image:-webkit-linear-gradient(top,#009cee,#0088d0);background-image:-o-linear-gradient(top,#009cee,#0088d0);background-image:linear-gradient(to bottom,#009cee,#0088d0);border-radius:3px;border:1px solid #007cbd}.comments-area .bypostauthor cite span{position:absolute;margin-left:5px;margin-left:0.357142857rem;padding:2px 5px;padding:0.142857143rem 0.357142857rem;font-size:10px;font-size:0.714285714rem}.comments-area .bypostauthor cite b{font-weight:bold}a.comment-reply-link,a.comment-edit-link{color:#686868;font-size:13px;font-size:0.928571429rem;line-height:1.846153846}a.comment-reply-link:hover,a.comment-edit-link:hover{color:#21759b}.commentlist .pingback{line-height:1.714285714;margin-bottom:24px;margin-bottom:1.714285714rem}#respond{margin-top:48px;margin-top:3.428571429rem}#respond h3#reply-title{font-size:16px;font-size:1.142857143rem;line-height:1.5}#respond h3#reply-title #cancel-comment-reply-link{margin-left:10px;margin-left:0.714285714rem;font-weight:normal;font-size:12px;font-size:0.857142857rem}#respond form{margin:24px 0;margin:1.714285714rem 0}#respond form p{margin:11px 0;margin:0.785714286rem 0}#respond form p.logged-in-as{margin-bottom:24px;margin-bottom:1.714285714rem}#respond form label{display:block;line-height:1.714285714}#respond form input[type="text"],#respond form textarea{-moz-box-sizing:border-box;box-sizing:border-box;font-size:12px;font-size:0.857142857rem;line-height:1.714285714;padding:10px;padding:0.714285714rem;width:100%}#respond form p.form-allowed-tags{margin:0;font-size:12px;font-size:0.857142857rem;line-height:2;color:#5e5e5e}#respond #wp-comment-cookies-consent{margin:0 10px 0 0}#respond .comment-form-cookies-consent label{display:inline}.required{color:red}.entry-page-image{margin-bottom:14px;margin-bottom:1rem}.template-front-page .site-content article{border:0;margin-bottom:0}.template-front-page .widget-area{clear:both;float:none;width:auto;padding-top:24px;padding-top:1.714285714rem;border-top:1px solid #ededed}.template-front-page .widget-area .widget li{margin:8px 0 0;margin:0.571428571rem 0 0;font-size:13px;font-size:0.928571429rem;line-height:1.714285714;list-style-type:square;list-style-position:inside}.template-front-page .widget-area .widget li a{color:#757575}.template-front-page .widget-area .widget li a:hover{color:#21759b}.template-front-page .widget-area .widget_text img{float:left;margin:8px 24px 8px 0;margin:0.571428571rem 1.714285714rem 0.571428571rem 0}.widget select{max-width:100%}.widget-area .widget ul ul{margin-left:12px;margin-left:0.857142857rem}.widget_rss li{margin:12px 0;margin:0.857142857rem 0}.widget_recent_entries .post-date,.widget_rss .rss-date{color:#aaa;font-size:11px;font-size:0.785714286rem;margin-left:12px;margin-left:0.857142857rem}.wp-calendar-nav,#wp-calendar{margin:0;width:100%;font-size:13px;font-size:0.928571429rem;line-height:1.846153846;color:#686868}#wp-calendar th,#wp-calendar td,#wp-calendar caption{text-align:left}.wp-calendar-nav{display:table}.wp-calendar-nav span{display:table-cell}.wp-calendar-nav-next,#wp-calendar #next{padding-right:24px;padding-right:1.714285714rem;text-align:right}.widget_search label{display:block;font-size:13px;font-size:0.928571429rem;line-height:1.846153846}.widget_twitter li{list-style-type:none}.widget_twitter .timesince{display:block;text-align:right}.tagcloud ul{list-style-type:none}.tagcloud ul li{display:inline-block}.widget-area .widget.widget_tag_cloud li{line-height:1}.template-front-page .widget-area .widget.widget_tag_cloud li{margin:0}.widget-area .gallery-columns-2.gallery-size-full .gallery-icon img,.widget-area .gallery-columns-3.gallery-size-full .gallery-icon img,.widget-area .gallery-columns-4.gallery-size-full .gallery-icon img,.widget-area .gallery-columns-5.gallery-size-full .gallery-icon img,.widget-area .gallery-columns-6 .gallery-icon img,.widget-area .gallery-columns-7 .gallery-icon img,.widget-area .gallery-columns-8 .gallery-icon img,.widget-area .gallery-columns-9 .gallery-icon img{height:auto;max-width:80%}img#wpstats{display:block;margin:0 auto 24px;margin:0 auto 1.714285714rem}@-ms-viewport{width:device-width}@viewport{width:device-width}@media screen and (min-width:600px){.author-avatar{float:left;margin-top:8px;margin-top:0.571428571rem}.author-description{float:right;width:80%}.site{margin:0 auto;max-width:960px;max-width:68.571428571rem;overflow:hidden}.site-content{float:left;width:65.104166667%}body.template-front-page .site-content,body.attachment .site-content,body.full-width .site-content{width:100%}.widget-area{float:right;width:26.041666667%}.site-header h1,.site-header h2{text-align:left}.site-header h1{font-size:26px;font-size:1.857142857rem;line-height:1.846153846;margin-bottom:0}.main-navigation ul.nav-menu,.main-navigation div.nav-menu>ul{border-bottom:1px solid #ededed;border-top:1px solid #ededed;display:inline-block!important;text-align:left;width:100%}.main-navigation ul{margin:0;text-indent:0}.main-navigation li a,.main-navigation li{display:inline-block;text-decoration:none}.main-navigation li a{border-bottom:0;color:#6a6a6a;line-height:3.692307692;text-transform:uppercase;white-space:nowrap}.main-navigation li a:hover,.main-navigation li a:focus{color:#000}.main-navigation li{margin:0 40px 0 0;margin:0 2.857142857rem 0 0;position:relative}.main-navigation li ul{margin:0;padding:0;position:absolute;top:100%;z-index:1;height:1px;width:1px;overflow:hidden;clip:rect(1px,1px,1px,1px)}.main-navigation li ul ul{top:0;left:100%}.main-navigation ul li:hover>ul,.main-navigation ul li:focus>ul,.main-navigation .focus>ul{border-left:0;clip:inherit;overflow:inherit;height:inherit;width:inherit}.main-navigation li ul li a{background:#efefef;border-bottom:1px solid #ededed;display:block;font-size:11px;font-size:0.785714286rem;line-height:2.181818182;padding:8px 10px;padding:0.571428571rem 0.714285714rem;width:180px;width:12.85714286rem;white-space:normal}.main-navigation li ul li a:hover,.main-navigation li ul li a:focus{background:#e3e3e3;color:#444}.main-navigation .current-menu-item>a,.main-navigation .current-menu-ancestor>a,.main-navigation .current_page_item>a,.main-navigation .current_page_ancestor>a{color:#636363;font-weight:bold}.menu-toggle{display:none}.entry-header .entry-title{font-size:22px;font-size:1.571428571rem}#respond form input[type="text"]{width:46.333333333%}#respond form textarea.blog-textarea{width:79.666666667%}.template-front-page .site-content,.template-front-page article{overflow:hidden}.template-front-page.has-post-thumbnail article{float:left;width:47.916666667%}.entry-page-image{float:right;margin-bottom:0;width:47.916666667%}.template-front-page .widget-area .widget,.template-front-page.two-sidebars .widget-area .front-widgets{float:left;width:51.875%;margin-bottom:24px;margin-bottom:1.714285714rem}.template-front-page .widget-area .widget:nth-child(odd){clear:right}.template-front-page .widget-area .widget:nth-child(even),.template-front-page.two-sidebars .widget-area .front-widgets + .front-widgets{float:right;width:39.0625%;margin:0 0 24px;margin:0 0 1.714285714rem}.template-front-page.two-sidebars .widget,.template-front-page.two-sidebars .widget:nth-child(even){float:none;width:auto}.commentlist .children{margin-left:48px;margin-left:3.428571429rem}}@media screen and (min-width:960px){body{background-color:#e6e6e6}body .site{padding:0 40px;padding:0 2.857142857rem;margin-top:48px;margin-top:3.428571429rem;margin-bottom:48px;margin-bottom:3.428571429rem;box-shadow:0 2px 6px rgba(100,100,100,0.3)}body.custom-background-empty{background-color:#fff}body.custom-background-empty .site,body.custom-background-white .site{padding:0;margin-top:0;margin-bottom:0;box-shadow:none}}@media print{body{background:none!important;color:#000;font-size:10pt}footer a[rel=bookmark]:link:after,footer a[rel=bookmark]:visited:after{content:" [" attr(href) "] "}a{text-decoration:none}.entry-content img,.comment-content img,.author-avatar img,img.wp-post-image{border-radius:0;box-shadow:none}.site{clear:both!important;display:block!important;float:none!important;max-width:100%;position:relative!important}.site-header{margin-bottom:72px;margin-bottom:5.142857143rem;text-align:left}.site-header h1{font-size:21pt;line-height:1;text-align:left}.site-header h2{color:#000;font-size:10pt;text-align:left}.site-header h1 a,.site-header h2 a{color:#000}.author-avatar,#colophon,#respond,.commentlist .comment-edit-link,.commentlist .reply,.entry-header .comments-link,.entry-meta .edit-link a,.page-link,.site-content nav,.widget-area,img.header-image,.main-navigation{display:none}.wrapper{border-top:none;box-shadow:none}.site-content{margin:0;width:auto}.entry-header .entry-title,.entry-title{font-size:21pt}footer.entry-meta,footer.entry-meta a{color:#444;font-size:10pt}.author-description{float:none;width:auto}.commentlist>li.comment{background:none;position:relative;width:auto}.commentlist .avatar{height:39px;left:2.2em;top:2.2em;width:39px}.comments-area article header cite,.comments-area article header time{margin-left:50px;margin-left:3.57142857rem}}.breadcrumb div{display:inline;font-size:13px;margin-left:-3px}#wp-auto-top{position:fixed;top:45%;right:50%;display:block;margin-right:-540px;z-index:9999}#wp-auto-top-top,#wp-auto-top-comment,#wp-auto-top-bottom{background:url(https://www.lylinux.org/wp-content/plugins/wp-auto-top/img/1.png) no-repeat;position:relative;cursor:pointer;height:25px;width:29px;margin:10px 0 0}#wp-auto-top-comment{background-position:left -30px;height:32px}#wp-auto-top-bottom{background-position:left -68px}#wp-auto-top-comment:hover{background-position:right -30px}#wp-auto-top-top:hover{background-position:right 0}#wp-auto-top-bottom:hover{background-position:right -68px}.widget-login{margin-top:15px!important}#comments{margin-top:20px}#pinglist-container{display:none}.comment-tabs{margin-bottom:20px;font-size:15px;border-bottom:2px solid #e5e5e5}.comment-tabs li{float:left;margin-bottom:-2px}.comment-tabs li a{display:block;padding:0 10px 10px;font-weight:600;color:#aaa;border-bottom:2px solid #e5e5e5}.comment-tabs li a:hover{color:#444;border-color:#ccc}.comment-tabs li span{margin-left:8px;padding:0 6px;border-radius:4px;background-color:#e5e5e5}.comment-tabs li i{margin-right:6px}.comment-tabs li.active a{color:#e8554e;border-bottom-color:#e8554e}.commentlist,.pinglist{margin-bottom:20px}.commentlist li,.pinglist li{padding-left:60px;font-size:14px;line-height:22px;font-weight:400}.commentlist .comment-body,.pinglist li{position:relative;padding-bottom:20px;clear:both;word-break:break-all}.commentlist .comment-author,.commentlist .comment-meta,.commentlist .comment-awaiting-moderation{float:left;display:block;font-size:13px;line-height:22px}.commentlist .comment-author{margin-right:6px}.commentlist .fn,.pinglist .ping-link{color:#444;font-size:13px;font-style:normal;font-weight:600}.commentlist .says{display:none}.commentlist .avatar{position:absolute;left:-60px;top:0;width:48px;height:48px;border-radius:100%}.commentlist .comment-meta:before,.pinglist .ping-meta:before{vertical-align:4%;margin-right:3px;font-size:10px;font-family:FontAwesome;color:#ccc}.commentlist .comment-meta a,.pinglist .ping-meta{color:#aaa}.commentlist .reply{font-size:13px;line-height:16px}.commentlist .reply a,.commentlist .comment-reply-chain{color:#aaa}.commentlist .reply a:hover,.commentlist .comment-reply-chain:hover{color:#444}.comment-awaiting-moderation{color:#e8554e;font-style:normal}.pinglist li{padding-left:0}.commentlist .comment-body p{margin-bottom:8px;color:#777;clear:both}.commentlist .comment-body strong{font-weight:600}.commentlist .comment-body ol li{margin-left:2em;padding:0;list-style:decimal}.commentlist .comment-body ul li{margin-left:2em;padding:0;list-style:square}.commentlist li.bypostauthor>.comment-body:after,.commentlist li.comment-author-admin>.comment-body:after{display:block;position:absolute;content:"\f040";width:12px;line-height:12px;font-style:normal;font-family:FontAwesome;text-align:center;color:#fff;background-color:#e8554e}.commentlist li.comment-author-admin>.comment-body:after{content:"\f005"}.commentlist li.bypostauthor>.comment-body:after,.commentlist li.comment-author-admin>.comment-body:after{padding:3px;top:32px;left:-28px;font-size:12px;border-radius:100%}.commentlist li li.bypostauthor>.comment-body:after,.commentlist li li.comment-author-admin>.comment-body:after{padding:2px;top:22px;left:-26px;font-size:10px;border-radius:100%}.commentlist li ul{}.commentlist li li{margin:0;padding-left:48px}.commentlist li li .avatar{top:0;left:-48px;width:36px;height:36px}.commentlist li li .comment-meta{left:70px}.comments-nav{margin-bottom:20px}.comments-nav a{font-weight:600}.comments-nav .nav-previous{float:left}.comments-nav .nav-next{float:right}.logged-in-as,.comment-notes,.form-allowed-tags{display:none}#respond{position:relative}#reply-title{margin-bottom:20px}li #reply-title{margin:0!important;padding:0;height:0;font-size:0;border-top:0}#cancel-comment-reply-link{float:right;bottom:26px;right:20px;font-size:12px;color:#999}#cancel-comment-reply-link:hover{color:#777}#commentform{margin-bottom:20px;padding:10px 20px 20px;border-radius:4px;background-color:#e5e5e5}#commentform p.comment-form-author{float:left;width:48%}#commentform p.comment-form-email{float:right;width:48%}#commentform p.comment-form-url,#commentform p.comment-form-comment{clear:both}#commentform label{display:block;padding:6px 0;font-weight:600}#commentform input[type="text"],#commentform textarea{max-width:100%;width:100%}#commentform textarea{height:100px}#commentform p.form-submit{margin-top:10px}.logged-in #reply-title{margin-bottom:20px}.logged-in #commentform p.comment-form-comment{margin-top:10px}.logged-in #commentform p.comment-form-comment label{display:none}.heading,#reply-title{margin-bottom:1em;font-size:18px;font-weight:600;text-transform:uppercase;color:#222}.heading i{margin-right:6px;font-size:22px}.group:before{content:"";display:table}.group:after{content:"";display:table;clear:both}.cancel-comment{margin:0;padding:0;border:0;font:inherit;vertical-align:baseline}#rocket{position:fixed;right:50px;bottom:50px;display:block;visibility:hidden;width:26px;height:48px;background:url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABoAAAB8CAYAAAB356CJAAAACXBIWXMAAAsTAAALEwEAmpwYAAAKTWlDQ1BQaG90b3Nob3AgSUNDIHByb2ZpbGUAAHjanVN3WJP3Fj7f92UPVkLY8LGXbIEAIiOsCMgQWaIQkgBhhBASQMWFiApWFBURnEhVxILVCkidiOKgKLhnQYqIWotVXDjuH9yntX167+3t+9f7vOec5/zOec8PgBESJpHmomoAOVKFPDrYH49PSMTJvYACFUjgBCAQ5svCZwXFAADwA3l4fnSwP/wBr28AAgBw1S4kEsfh/4O6UCZXACCRAOAiEucLAZBSAMguVMgUAMgYALBTs2QKAJQAAGx5fEIiAKoNAOz0ST4FANipk9wXANiiHKkIAI0BAJkoRyQCQLsAYFWBUiwCwMIAoKxAIi4EwK4BgFm2MkcCgL0FAHaOWJAPQGAAgJlCLMwAIDgCAEMeE80DIEwDoDDSv+CpX3CFuEgBAMDLlc2XS9IzFLiV0Bp38vDg4iHiwmyxQmEXKRBmCeQinJebIxNI5wNMzgwAABr50cH+OD+Q5+bk4eZm52zv9MWi/mvwbyI+IfHf/ryMAgQAEE7P79pf5eXWA3DHAbB1v2upWwDaVgBo3/ldM9sJoFoK0Hr5i3k4/EAenqFQyDwdHAoLC+0lYqG9MOOLPv8z4W/gi372/EAe/tt68ABxmkCZrcCjg/1xYW52rlKO58sEQjFu9+cj/seFf/2OKdHiNLFcLBWK8ViJuFAiTcd5uVKRRCHJleIS6X8y8R+W/QmTdw0ArIZPwE62B7XLbMB+7gECiw5Y0nYAQH7zLYwaC5EAEGc0Mnn3AACTv/mPQCsBAM2XpOMAALzoGFyolBdMxggAAESggSqwQQcMwRSswA6cwR28wBcCYQZEQAwkwDwQQgbkgBwKoRiWQRlUwDrYBLWwAxqgEZrhELTBMTgN5+ASXIHrcBcGYBiewhi8hgkEQcgIE2EhOogRYo7YIs4IF5mOBCJhSDSSgKQg6YgUUSLFyHKkAqlCapFdSCPyLXIUOY1cQPqQ28ggMor8irxHMZSBslED1AJ1QLmoHxqKxqBz0XQ0D12AlqJr0Rq0Hj2AtqKn0UvodXQAfYqOY4DRMQ5mjNlhXIyHRWCJWBomxxZj5Vg1Vo81Yx1YN3YVG8CeYe8IJAKLgBPsCF6EEMJsgpCQR1hMWEOoJewjtBK6CFcJg4Qxwicik6hPtCV6EvnEeGI6sZBYRqwm7iEeIZ4lXicOE1+TSCQOyZLkTgohJZAySQtJa0jbSC2kU6Q+0hBpnEwm65Btyd7kCLKArCCXkbeQD5BPkvvJw+S3FDrFiOJMCaIkUqSUEko1ZT/lBKWfMkKZoKpRzame1AiqiDqfWkltoHZQL1OHqRM0dZolzZsWQ8ukLaPV0JppZ2n3aC/pdLoJ3YMeRZfQl9Jr6Afp5+mD9HcMDYYNg8dIYigZaxl7GacYtxkvmUymBdOXmchUMNcyG5lnmA+Yb1VYKvYqfBWRyhKVOpVWlX6V56pUVXNVP9V5qgtUq1UPq15WfaZGVbNQ46kJ1Bar1akdVbupNq7OUndSj1DPUV+jvl/9gvpjDbKGhUaghkijVGO3xhmNIRbGMmXxWELWclYD6yxrmE1iW7L57Ex2Bfsbdi97TFNDc6pmrGaRZp3mcc0BDsax4PA52ZxKziHODc57LQMtPy2x1mqtZq1+rTfaetq+2mLtcu0W7eva73VwnUCdLJ31Om0693UJuja6UbqFutt1z+o+02PreekJ9cr1Dund0Uf1bfSj9Rfq79bv0R83MDQINpAZbDE4Y/DMkGPoa5hpuNHwhOGoEctoupHEaKPRSaMnuCbuh2fjNXgXPmasbxxirDTeZdxrPGFiaTLbpMSkxeS+Kc2Ua5pmutG003TMzMgs3KzYrMnsjjnVnGueYb7ZvNv8jYWlRZzFSos2i8eW2pZ8ywWWTZb3rJhWPlZ5VvVW16xJ1lzrLOtt1ldsUBtXmwybOpvLtqitm63Edptt3xTiFI8p0in1U27aMez87ArsmuwG7Tn2YfYl9m32zx3MHBId1jt0O3xydHXMdmxwvOuk4TTDqcSpw+lXZxtnoXOd8zUXpkuQyxKXdpcXU22niqdun3rLleUa7rrStdP1o5u7m9yt2W3U3cw9xX2r+00umxvJXcM970H08PdY4nHM452nm6fC85DnL152Xlle+70eT7OcJp7WMG3I28Rb4L3Le2A6Pj1l+s7pAz7GPgKfep+Hvqa+It89viN+1n6Zfgf8nvs7+sv9j/i/4XnyFvFOBWABwQHlAb2BGoGzA2sDHwSZBKUHNQWNBbsGLww+FUIMCQ1ZH3KTb8AX8hv5YzPcZyya0RXKCJ0VWhv6MMwmTB7WEY6GzwjfEH5vpvlM6cy2CIjgR2yIuB9pGZkX+X0UKSoyqi7qUbRTdHF09yzWrORZ+2e9jvGPqYy5O9tqtnJ2Z6xqbFJsY+ybuIC4qriBeIf4RfGXEnQTJAntieTE2MQ9ieNzAudsmjOc5JpUlnRjruXcorkX5unOy553PFk1WZB8OIWYEpeyP+WDIEJQLxhP5aduTR0T8oSbhU9FvqKNolGxt7hKPJLmnVaV9jjdO31D+miGT0Z1xjMJT1IreZEZkrkj801WRNberM/ZcdktOZSclJyjUg1plrQr1zC3KLdPZisrkw3keeZtyhuTh8r35CP5c/PbFWyFTNGjtFKuUA4WTC+oK3hbGFt4uEi9SFrUM99m/ur5IwuCFny9kLBQuLCz2Lh4WfHgIr9FuxYji1MXdy4xXVK6ZHhp8NJ9y2jLspb9UOJYUlXyannc8o5Sg9KlpUMrglc0lamUycturvRauWMVYZVkVe9ql9VbVn8qF5VfrHCsqK74sEa45uJXTl/VfPV5bdra3kq3yu3rSOuk626s91m/r0q9akHV0IbwDa0b8Y3lG19tSt50oXpq9Y7NtM3KzQM1YTXtW8y2rNvyoTaj9nqdf13LVv2tq7e+2Sba1r/dd3vzDoMdFTve75TsvLUreFdrvUV99W7S7oLdjxpiG7q/5n7duEd3T8Wej3ulewf2Re/ranRvbNyvv7+yCW1SNo0eSDpw5ZuAb9qb7Zp3tXBaKg7CQeXBJ9+mfHvjUOihzsPcw83fmX+39QjrSHkr0jq/dawto22gPaG97+iMo50dXh1Hvrf/fu8x42N1xzWPV56gnSg98fnkgpPjp2Snnp1OPz3Umdx590z8mWtdUV29Z0PPnj8XdO5Mt1/3yfPe549d8Lxw9CL3Ytslt0utPa49R35w/eFIr1tv62X3y+1XPK509E3rO9Hv03/6asDVc9f41y5dn3m978bsG7duJt0cuCW69fh29u0XdwruTNxdeo94r/y+2v3qB/oP6n+0/rFlwG3g+GDAYM/DWQ/vDgmHnv6U/9OH4dJHzEfVI0YjjY+dHx8bDRq98mTOk+GnsqcTz8p+Vv9563Or59/94vtLz1j82PAL+YvPv655qfNy76uprzrHI8cfvM55PfGm/K3O233vuO+638e9H5ko/ED+UPPR+mPHp9BP9z7nfP78L/eE8/sl0p8zAAAAIGNIUk0AAHolAACAgwAA+f8AAIDpAAB1MAAA6mAAADqYAAAXb5JfxUYAAAbdSURBVHja5NlbbBRVGAfw5VID+LAK8cEoxqTgmw8kPPhwipTGxJTDUAVBQBMNKtZboiDE2ES8pFEjGhNkkCrin3JbZo4YCqloUOoKJCDIRWyRAgW6R3dobU2bJtj6+eCMTqczs2d3Zh6Mm3xpdvc7++vMnHNmzvlSRJQqJgA8B8AC8EQx7YoBxgD4CAC54i0Ao2KDAIwCsNGDOPF6nNBLAYgTiyNDAKYDGCwA/Q7gtpIhAKMBHC+AOPF5FGiBIuLEXaVCR4uEzKIhAHcViRCAP4OuVRi0pgSIACwvFurw/ohhGJTP56m7u5vy+TwZhuEHHVKGANzmh3R3d48IH2wQwPWq0CIv5ByJN/L5vN9RzVKF3vQ29kOcULlOQZAZ8YjWq0JHI1wjAvClKnTJr+sq9joCcEoV6itxDDmRU4UoYvT8f6GeiFCXKpSLCJ1XhU5GhI6oQs0RoT2qUENESFeFlkeEXlCFZkeEqlWhWyNCtxSE7GdsPSL0AYAxgRCACQB2xzAzEAABYMIIyEYOxIQ4sR/AOC+UiRlxYvM/EID5CSFO1DjQoYShFmfFMJgwdC0FYHzCCAEYck5dZ8LQWQdCwpAe19xWKCocqAzA1YSQiwBGuwfs2yHJpwDcEBJHQtqu9s4MU0KSHy+wBF0c1NsATPabVL/ye6IBML4AVAbgik/bvUGz9zyf5HrFTY9VPm0XBkFlAH7xrN5uVYQmAuh3P0Q6M3fQje81V/LWIne+1gY9oPglTwLQai+Wby8SugnAj/Y2W7nqqnyUz2cagDb7P24DoAXshI2Nsl9XZXdXb/etintjMBswVrJxQ0H3rMG4oYEAaOA/e+rqAqC6uKHyAKg8VsjGDnqQg7Hve9tQrQeqTQpKuybOfgDpRCDParAhkZKBC5pmQ9MShWysvtg2RSOZTKYu0WqLYRhjTdMUQghqbGxMrtpimuYuIQQJIWj79u3JVFsMw3jHQYQQfhuC0asthmFUCiGG3JAQgjZv3hxftaW5uXmMEOJnLyKEoK1bt8ZXbTEMY5kfIoSgHTt2xFdtEUK0BkE7d+6Mp9piGMY9QYgQgkzTjKfaYprmJvcPn/vhOHV8+D511j5EuUWzqXPZEmpd9x59/102WrVFCPGrG7myopZkzUyS2ox/Ijf3bjq/8mkvpl5tMQzjDvfRdKx7l+TcmZR7bAH1nThGf167Rn0njlHn0gcoV1NJrWvXlFZtMQzjaTfU+eQSknMqqP+n0+R+9Z05RXJOBXUsW1xatcUwjAY3lLu/iuScCvJ7SW0GXVlUXVq1xTTN/cOghfcGH5E2w++I1Kot3vFzceP6vy++5xrlli6gXM1MOvOxXlq1RQiR946by6tXkpw7vNfJmko698qL1NzUVFq1RQgx4DdIL2z7lDqfephyD2l05dlH6ELjRj9EvdoSNiMozA7qtQlVSAjx34H6IkJdqlBXROi86oBtjwgdUYUOR4T2qEJmREhXnVTrI0IvqEJLIg7YalWoXAUKqSwXrrZIzsZIzvSfT5woCTr2zdckOftAchZcbZGcTZCc7ZacUfu+vQWhTCYzAjq9vZEkZyQ5E5KzkdUWGzlgJ9GFjetLgtrerXcgkpztl5yN80IZVwJdWvVMQcizqiAAdPHZR90QSc7+rbZIzuZ7vqTcfZXUdvp0KOR9/j78bQvlaiq9EEnOahzokM+X1P7FnlBoy5Ytw69P4yd+CEnOWlKSs9GSs0G/hI41bxQ1WNtffj4IupaSnI0P+JJyD1bT8aNHlbr24ZYWys2rCoKGnFPXGYS1N+1S6nFnPtaDEJKcnXUgBCVdfrHWF9q2bdswqGPZ4jBId6DZIUnUnm0J7Qgnd5lhCEnOKhyoTHJ2NSjx0qurQifTCytqw5CLkrPR7gH7dkhy6HaZ5OzbkLarvTPDlJDkRQWg+UG9TXI22W9S/conWUrOrisAjbVPkbft3qDZe55P8qsqmx6SsxU+bRcGQWWSs19ciX9Izm5WhG6UnPW52vY4M3fQje81V3JR1RbJ2Vr32Cl0h50kOWuVnHVIzm4vErpJcvaj5MySnKlVWyRnw7bHLF1L9WbTWm823dabTZP9V7N0bUQ7yVnp1RZL16p69k0eshHqzaapZ9/kIUvX4q22WLqW7cpMJzfUlZlOlq5l44YGrQ3VwyBrQzVZujYYNzRg6Rr1tkz8G2qZSJaukaVrA7GfOkvX6LemqdSbTdNvTVMdKPZTV2fpGl3dNIt6s2m6ummWA9XFDZXbP0zdn93pIGTpWnncUMrStYMugOz3qSSgWg9UmxSUtnSt30b67feJQClL1xpsqMH5LClomg1NSxpKWbpW736v0v6vAQCo4CbBrd8RBQAAAABJRU5ErkJggg==") no-repeat 50% 0;cursor:pointer;-webkit-transition:all 0s;transition:all 0s}#rocket:hover{background-position:50% -62px}#rocket.show{visibility:visible;opacity:1}#rocket.move{background-position:50% -62px;-webkit-animation:toTop .8s ease-in;animation:toTop .8s ease-in;animation-fill-mode:forwards;-webkit-animation-fill-mode:forwards}.comment-markdown{float:right;font-size:small}.breadcrumb{margin-bottom:20px;list-style:none;border-radius:4px}.breadcrumb>li{display:inline-block}.breadcrumb>li + li:before{color:#ccc;content:"/\00a0"}.breadcrumb>.active{color:#777}.break_line{height:1px;border:none} \ No newline at end of file diff --git a/src/DjangoBlog/collectedstatic/CACHE/css/output.bdd96b6d5fb9.css b/src/DjangoBlog/collectedstatic/CACHE/css/output.bdd96b6d5fb9.css new file mode 100644 index 0000000..1c41621 --- /dev/null +++ b/src/DjangoBlog/collectedstatic/CACHE/css/output.bdd96b6d5fb9.css @@ -0,0 +1 @@ +.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('/static/blog/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}.codehilite .hll{background-color:#ffffcc}.codehilite{background:#ffffff}.codehilite .c{color:#177500}.codehilite .err{color:#000000}.codehilite .k{color:#A90D91}.codehilite .l{color:#1C01CE}.codehilite .n{color:#000000}.codehilite .o{color:#000000}.codehilite .ch{color:#177500}.codehilite .cm{color:#177500}.codehilite .cp{color:#633820}.codehilite .cpf{color:#177500}.codehilite .c1{color:#177500}.codehilite .cs{color:#177500}.codehilite .kc{color:#A90D91}.codehilite .kd{color:#A90D91}.codehilite .kn{color:#A90D91}.codehilite .kp{color:#A90D91}.codehilite .kr{color:#A90D91}.codehilite .kt{color:#A90D91}.codehilite .ld{color:#1C01CE}.codehilite .m{color:#1C01CE}.codehilite .s{color:#C41A16}.codehilite .na{color:#836C28}.codehilite .nb{color:#A90D91}.codehilite .nc{color:#3F6E75}.codehilite .no{color:#000000}.codehilite .nd{color:#000000}.codehilite .ni{color:#000000}.codehilite .ne{color:#000000}.codehilite .nf{color:#000000}.codehilite .nl{color:#000000}.codehilite .nn{color:#000000}.codehilite .nx{color:#000000}.codehilite .py{color:#000000}.codehilite .nt{color:#000000}.codehilite .nv{color:#000000}.codehilite .ow{color:#000000}.codehilite .mb{color:#1C01CE}.codehilite .mf{color:#1C01CE}.codehilite .mh{color:#1C01CE}.codehilite .mi{color:#1C01CE}.codehilite .mo{color:#1C01CE}.codehilite .sb{color:#C41A16}.codehilite .sc{color:#2300CE}.codehilite .sd{color:#C41A16}.codehilite .s2{color:#C41A16}.codehilite .se{color:#C41A16}.codehilite .sh{color:#C41A16}.codehilite .si{color:#C41A16}.codehilite .sx{color:#C41A16}.codehilite .sr{color:#C41A16}.codehilite .s1{color:#C41A16}.codehilite .ss{color:#C41A16}.codehilite .bp{color:#5B269A}.codehilite .vc{color:#000000}.codehilite .vg{color:#000000}.codehilite .vi{color:#000000}.codehilite .il{color:#1C01CE}#nprogress{pointer-events:none}#nprogress .bar{background:red;position:fixed;z-index:1031;top:0;left:0;width:100%;height:2px}#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)}#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)}} \ No newline at end of file diff --git a/src/DjangoBlog/collectedstatic/CACHE/js/output.bc55ccd28723.js b/src/DjangoBlog/collectedstatic/CACHE/js/output.bc55ccd28723.js new file mode 100644 index 0000000..7bb9905 --- /dev/null +++ b/src/DjangoBlog/collectedstatic/CACHE/js/output.bc55ccd28723.js @@ -0,0 +1,36 @@ +/*! jQuery v3.6.0 | (c) OpenJS Foundation and other contributors | jquery.org/license */!function(e,t){"use strict";"object"==typeof module&&"object"==typeof module.exports?module.exports=e.document?t(e,!0):function(e){if(!e.document)throw new Error("jQuery requires a window with a document");return t(e)}:t(e)}("undefined"!=typeof window?window:this,function(C,e){"use strict";var t=[],r=Object.getPrototypeOf,s=t.slice,g=t.flat?function(e){return t.flat.call(e)}:function(e){return t.concat.apply([],e)},u=t.push,i=t.indexOf,n={},o=n.toString,v=n.hasOwnProperty,a=v.toString,l=a.call(Object),y={},m=function(e){return"function"==typeof e&&"number"!=typeof e.nodeType&&"function"!=typeof e.item},x=function(e){return null!=e&&e===e.window},E=C.document,c={type:!0,src:!0,nonce:!0,noModule:!0};function b(e,t,n){var r,i,o=(n=n||E).createElement("script");if(o.text=e,t)for(r in c)(i=t[r]||t.getAttribute&&t.getAttribute(r))&&o.setAttribute(r,i);n.head.appendChild(o).parentNode.removeChild(o)}function w(e){return null==e?e+"":"object"==typeof e||"function"==typeof e?n[o.call(e)]||"object":typeof e}var f="3.6.0",S=function(e,t){return new S.fn.init(e,t)};function p(e){var t=!!e&&"length"in e&&e.length,n=w(e);return!m(e)&&!x(e)&&("array"===n||0===t||"number"==typeof t&&0+~]|"+M+")"+M+"*"),U=new RegExp(M+"|>"),X=new RegExp(F),V=new RegExp("^"+I+"$"),G={ID:new RegExp("^#("+I+")"),CLASS:new RegExp("^\\.("+I+")"),TAG:new RegExp("^("+I+"|[*])"),ATTR:new RegExp("^"+W),PSEUDO:new RegExp("^"+F),CHILD:new RegExp("^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\("+M+"*(even|odd|(([+-]|)(\\d*)n|)"+M+"*(?:([+-]|)"+M+"*(\\d+)|))"+M+"*\\)|)","i"),bool:new RegExp("^(?:"+R+")$","i"),needsContext:new RegExp("^"+M+"*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\("+M+"*((?:-\\d)?\\d*)"+M+"*\\)|)(?=[^-]|$)","i")},Y=/HTML$/i,Q=/^(?:input|select|textarea|button)$/i,J=/^h\d$/i,K=/^[^{]+\{\s*\[native \w/,Z=/^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/,ee=/[+~]/,te=new RegExp("\\\\[\\da-fA-F]{1,6}"+M+"?|\\\\([^\\r\\n\\f])","g"),ne=function(e,t){var n="0x"+e.slice(1)-65536;return t||(n<0?String.fromCharCode(n+65536):String.fromCharCode(n>>10|55296,1023&n|56320))},re=/([\0-\x1f\x7f]|^-?\d)|^-$|[^\0-\x1f\x7f-\uFFFF\w-]/g,ie=function(e,t){return t?"\0"===e?"\ufffd":e.slice(0,-1)+"\\"+e.charCodeAt(e.length-1).toString(16)+" ":"\\"+e},oe=function(){T()},ae=be(function(e){return!0===e.disabled&&"fieldset"===e.nodeName.toLowerCase()},{dir:"parentNode",next:"legend"});try{H.apply(t=O.call(p.childNodes),p.childNodes),t[p.childNodes.length].nodeType}catch(e){H={apply:t.length?function(e,t){L.apply(e,O.call(t))}:function(e,t){var n=e.length,r=0;while(e[n++]=t[r++]);e.length=n-1}}}function se(t,e,n,r){var i,o,a,s,u,l,c,f=e&&e.ownerDocument,p=e?e.nodeType:9;if(n=n||[],"string"!=typeof t||!t||1!==p&&9!==p&&11!==p)return n;if(!r&&(T(e),e=e||C,E)){if(11!==p&&(u=Z.exec(t)))if(i=u[1]){if(9===p){if(!(a=e.getElementById(i)))return n;if(a.id===i)return n.push(a),n}else if(f&&(a=f.getElementById(i))&&y(e,a)&&a.id===i)return n.push(a),n}else{if(u[2])return H.apply(n,e.getElementsByTagName(t)),n;if((i=u[3])&&d.getElementsByClassName&&e.getElementsByClassName)return H.apply(n,e.getElementsByClassName(i)),n}if(d.qsa&&!N[t+" "]&&(!v||!v.test(t))&&(1!==p||"object"!==e.nodeName.toLowerCase())){if(c=t,f=e,1===p&&(U.test(t)||z.test(t))){(f=ee.test(t)&&ye(e.parentNode)||e)===e&&d.scope||((s=e.getAttribute("id"))?s=s.replace(re,ie):e.setAttribute("id",s=S)),o=(l=h(t)).length;while(o--)l[o]=(s?"#"+s:":scope")+" "+xe(l[o]);c=l.join(",")}try{return H.apply(n,f.querySelectorAll(c)),n}catch(e){N(t,!0)}finally{s===S&&e.removeAttribute("id")}}}return g(t.replace($,"$1"),e,n,r)}function ue(){var r=[];return function e(t,n){return r.push(t+" ")>b.cacheLength&&delete e[r.shift()],e[t+" "]=n}}function le(e){return e[S]=!0,e}function ce(e){var t=C.createElement("fieldset");try{return!!e(t)}catch(e){return!1}finally{t.parentNode&&t.parentNode.removeChild(t),t=null}}function fe(e,t){var n=e.split("|"),r=n.length;while(r--)b.attrHandle[n[r]]=t}function pe(e,t){var n=t&&e,r=n&&1===e.nodeType&&1===t.nodeType&&e.sourceIndex-t.sourceIndex;if(r)return r;if(n)while(n=n.nextSibling)if(n===t)return-1;return e?1:-1}function de(t){return function(e){return"input"===e.nodeName.toLowerCase()&&e.type===t}}function he(n){return function(e){var t=e.nodeName.toLowerCase();return("input"===t||"button"===t)&&e.type===n}}function ge(t){return function(e){return"form"in e?e.parentNode&&!1===e.disabled?"label"in e?"label"in e.parentNode?e.parentNode.disabled===t:e.disabled===t:e.isDisabled===t||e.isDisabled!==!t&&ae(e)===t:e.disabled===t:"label"in e&&e.disabled===t}}function ve(a){return le(function(o){return o=+o,le(function(e,t){var n,r=a([],e.length,o),i=r.length;while(i--)e[n=r[i]]&&(e[n]=!(t[n]=e[n]))})})}function ye(e){return e&&"undefined"!=typeof e.getElementsByTagName&&e}for(e in d=se.support={},i=se.isXML=function(e){var t=e&&e.namespaceURI,n=e&&(e.ownerDocument||e).documentElement;return!Y.test(t||n&&n.nodeName||"HTML")},T=se.setDocument=function(e){var t,n,r=e?e.ownerDocument||e:p;return r!=C&&9===r.nodeType&&r.documentElement&&(a=(C=r).documentElement,E=!i(C),p!=C&&(n=C.defaultView)&&n.top!==n&&(n.addEventListener?n.addEventListener("unload",oe,!1):n.attachEvent&&n.attachEvent("onunload",oe)),d.scope=ce(function(e){return a.appendChild(e).appendChild(C.createElement("div")),"undefined"!=typeof e.querySelectorAll&&!e.querySelectorAll(":scope fieldset div").length}),d.attributes=ce(function(e){return e.className="i",!e.getAttribute("className")}),d.getElementsByTagName=ce(function(e){return e.appendChild(C.createComment("")),!e.getElementsByTagName("*").length}),d.getElementsByClassName=K.test(C.getElementsByClassName),d.getById=ce(function(e){return a.appendChild(e).id=S,!C.getElementsByName||!C.getElementsByName(S).length}),d.getById?(b.filter.ID=function(e){var t=e.replace(te,ne);return function(e){return e.getAttribute("id")===t}},b.find.ID=function(e,t){if("undefined"!=typeof t.getElementById&&E){var n=t.getElementById(e);return n?[n]:[]}}):(b.filter.ID=function(e){var n=e.replace(te,ne);return function(e){var t="undefined"!=typeof e.getAttributeNode&&e.getAttributeNode("id");return t&&t.value===n}},b.find.ID=function(e,t){if("undefined"!=typeof t.getElementById&&E){var n,r,i,o=t.getElementById(e);if(o){if((n=o.getAttributeNode("id"))&&n.value===e)return[o];i=t.getElementsByName(e),r=0;while(o=i[r++])if((n=o.getAttributeNode("id"))&&n.value===e)return[o]}return[]}}),b.find.TAG=d.getElementsByTagName?function(e,t){return"undefined"!=typeof t.getElementsByTagName?t.getElementsByTagName(e):d.qsa?t.querySelectorAll(e):void 0}:function(e,t){var n,r=[],i=0,o=t.getElementsByTagName(e);if("*"===e){while(n=o[i++])1===n.nodeType&&r.push(n);return r}return o},b.find.CLASS=d.getElementsByClassName&&function(e,t){if("undefined"!=typeof t.getElementsByClassName&&E)return t.getElementsByClassName(e)},s=[],v=[],(d.qsa=K.test(C.querySelectorAll))&&(ce(function(e){var t;a.appendChild(e).innerHTML="",e.querySelectorAll("[msallowcapture^='']").length&&v.push("[*^$]="+M+"*(?:''|\"\")"),e.querySelectorAll("[selected]").length||v.push("\\["+M+"*(?:value|"+R+")"),e.querySelectorAll("[id~="+S+"-]").length||v.push("~="),(t=C.createElement("input")).setAttribute("name",""),e.appendChild(t),e.querySelectorAll("[name='']").length||v.push("\\["+M+"*name"+M+"*="+M+"*(?:''|\"\")"),e.querySelectorAll(":checked").length||v.push(":checked"),e.querySelectorAll("a#"+S+"+*").length||v.push(".#.+[+~]"),e.querySelectorAll("\\\f"),v.push("[\\r\\n\\f]")}),ce(function(e){e.innerHTML="";var t=C.createElement("input");t.setAttribute("type","hidden"),e.appendChild(t).setAttribute("name","D"),e.querySelectorAll("[name=d]").length&&v.push("name"+M+"*[*^$|!~]?="),2!==e.querySelectorAll(":enabled").length&&v.push(":enabled",":disabled"),a.appendChild(e).disabled=!0,2!==e.querySelectorAll(":disabled").length&&v.push(":enabled",":disabled"),e.querySelectorAll("*,:x"),v.push(",.*:")})),(d.matchesSelector=K.test(c=a.matches||a.webkitMatchesSelector||a.mozMatchesSelector||a.oMatchesSelector||a.msMatchesSelector))&&ce(function(e){d.disconnectedMatch=c.call(e,"*"),c.call(e,"[s!='']:x"),s.push("!=",F)}),v=v.length&&new RegExp(v.join("|")),s=s.length&&new RegExp(s.join("|")),t=K.test(a.compareDocumentPosition),y=t||K.test(a.contains)?function(e,t){var n=9===e.nodeType?e.documentElement:e,r=t&&t.parentNode;return e===r||!(!r||1!==r.nodeType||!(n.contains?n.contains(r):e.compareDocumentPosition&&16&e.compareDocumentPosition(r)))}:function(e,t){if(t)while(t=t.parentNode)if(t===e)return!0;return!1},j=t?function(e,t){if(e===t)return l=!0,0;var n=!e.compareDocumentPosition-!t.compareDocumentPosition;return n||(1&(n=(e.ownerDocument||e)==(t.ownerDocument||t)?e.compareDocumentPosition(t):1)||!d.sortDetached&&t.compareDocumentPosition(e)===n?e==C||e.ownerDocument==p&&y(p,e)?-1:t==C||t.ownerDocument==p&&y(p,t)?1:u?P(u,e)-P(u,t):0:4&n?-1:1)}:function(e,t){if(e===t)return l=!0,0;var n,r=0,i=e.parentNode,o=t.parentNode,a=[e],s=[t];if(!i||!o)return e==C?-1:t==C?1:i?-1:o?1:u?P(u,e)-P(u,t):0;if(i===o)return pe(e,t);n=e;while(n=n.parentNode)a.unshift(n);n=t;while(n=n.parentNode)s.unshift(n);while(a[r]===s[r])r++;return r?pe(a[r],s[r]):a[r]==p?-1:s[r]==p?1:0}),C},se.matches=function(e,t){return se(e,null,null,t)},se.matchesSelector=function(e,t){if(T(e),d.matchesSelector&&E&&!N[t+" "]&&(!s||!s.test(t))&&(!v||!v.test(t)))try{var n=c.call(e,t);if(n||d.disconnectedMatch||e.document&&11!==e.document.nodeType)return n}catch(e){N(t,!0)}return 0":{dir:"parentNode",first:!0}," ":{dir:"parentNode"},"+":{dir:"previousSibling",first:!0},"~":{dir:"previousSibling"}},preFilter:{ATTR:function(e){return e[1]=e[1].replace(te,ne),e[3]=(e[3]||e[4]||e[5]||"").replace(te,ne),"~="===e[2]&&(e[3]=" "+e[3]+" "),e.slice(0,4)},CHILD:function(e){return e[1]=e[1].toLowerCase(),"nth"===e[1].slice(0,3)?(e[3]||se.error(e[0]),e[4]=+(e[4]?e[5]+(e[6]||1):2*("even"===e[3]||"odd"===e[3])),e[5]=+(e[7]+e[8]||"odd"===e[3])):e[3]&&se.error(e[0]),e},PSEUDO:function(e){var t,n=!e[6]&&e[2];return G.CHILD.test(e[0])?null:(e[3]?e[2]=e[4]||e[5]||"":n&&X.test(n)&&(t=h(n,!0))&&(t=n.indexOf(")",n.length-t)-n.length)&&(e[0]=e[0].slice(0,t),e[2]=n.slice(0,t)),e.slice(0,3))}},filter:{TAG:function(e){var t=e.replace(te,ne).toLowerCase();return"*"===e?function(){return!0}:function(e){return e.nodeName&&e.nodeName.toLowerCase()===t}},CLASS:function(e){var t=m[e+" "];return t||(t=new RegExp("(^|"+M+")"+e+"("+M+"|$)"))&&m(e,function(e){return t.test("string"==typeof e.className&&e.className||"undefined"!=typeof e.getAttribute&&e.getAttribute("class")||"")})},ATTR:function(n,r,i){return function(e){var t=se.attr(e,n);return null==t?"!="===r:!r||(t+="","="===r?t===i:"!="===r?t!==i:"^="===r?i&&0===t.indexOf(i):"*="===r?i&&-1:\x20\t\r\n\f]*)[\x20\t\r\n\f]*\/?>(?:<\/\1>|)$/i;function j(e,n,r){return m(n)?S.grep(e,function(e,t){return!!n.call(e,t,e)!==r}):n.nodeType?S.grep(e,function(e){return e===n!==r}):"string"!=typeof n?S.grep(e,function(e){return-1)[^>]*|#([\w-]+))$/;(S.fn.init=function(e,t,n){var r,i;if(!e)return this;if(n=n||D,"string"==typeof e){if(!(r="<"===e[0]&&">"===e[e.length-1]&&3<=e.length?[null,e,null]:q.exec(e))||!r[1]&&t)return!t||t.jquery?(t||n).find(e):this.constructor(t).find(e);if(r[1]){if(t=t instanceof S?t[0]:t,S.merge(this,S.parseHTML(r[1],t&&t.nodeType?t.ownerDocument||t:E,!0)),N.test(r[1])&&S.isPlainObject(t))for(r in t)m(this[r])?this[r](t[r]):this.attr(r,t[r]);return this}return(i=E.getElementById(r[2]))&&(this[0]=i,this.length=1),this}return e.nodeType?(this[0]=e,this.length=1,this):m(e)?void 0!==n.ready?n.ready(e):e(S):S.makeArray(e,this)}).prototype=S.fn,D=S(E);var L=/^(?:parents|prev(?:Until|All))/,H={children:!0,contents:!0,next:!0,prev:!0};function O(e,t){while((e=e[t])&&1!==e.nodeType);return e}S.fn.extend({has:function(e){var t=S(e,this),n=t.length;return this.filter(function(){for(var e=0;e\x20\t\r\n\f]*)/i,he=/^$|^module$|\/(?:java|ecma)script/i;ce=E.createDocumentFragment().appendChild(E.createElement("div")),(fe=E.createElement("input")).setAttribute("type","radio"),fe.setAttribute("checked","checked"),fe.setAttribute("name","t"),ce.appendChild(fe),y.checkClone=ce.cloneNode(!0).cloneNode(!0).lastChild.checked,ce.innerHTML="",y.noCloneChecked=!!ce.cloneNode(!0).lastChild.defaultValue,ce.innerHTML="",y.option=!!ce.lastChild;var ge={thead:[1,"","
"],col:[2,"","
"],tr:[2,"","
"],td:[3,"","
"],_default:[0,"",""]};function ve(e,t){var n;return n="undefined"!=typeof e.getElementsByTagName?e.getElementsByTagName(t||"*"):"undefined"!=typeof e.querySelectorAll?e.querySelectorAll(t||"*"):[],void 0===t||t&&A(e,t)?S.merge([e],n):n}function ye(e,t){for(var n=0,r=e.length;n",""]);var me=/<|&#?\w+;/;function xe(e,t,n,r,i){for(var o,a,s,u,l,c,f=t.createDocumentFragment(),p=[],d=0,h=e.length;d\s*$/g;function je(e,t){return A(e,"table")&&A(11!==t.nodeType?t:t.firstChild,"tr")&&S(e).children("tbody")[0]||e}function De(e){return e.type=(null!==e.getAttribute("type"))+"/"+e.type,e}function qe(e){return"true/"===(e.type||"").slice(0,5)?e.type=e.type.slice(5):e.removeAttribute("type"),e}function Le(e,t){var n,r,i,o,a,s;if(1===t.nodeType){if(Y.hasData(e)&&(s=Y.get(e).events))for(i in Y.remove(t,"handle events"),s)for(n=0,r=s[i].length;n").attr(n.scriptAttrs||{}).prop({charset:n.scriptCharset,src:n.url}).on("load error",i=function(e){r.remove(),i=null,e&&t("error"===e.type?404:200,e.type)}),E.head.appendChild(r[0])},abort:function(){i&&i()}}});var _t,zt=[],Ut=/(=)\?(?=&|$)|\?\?/;S.ajaxSetup({jsonp:"callback",jsonpCallback:function(){var e=zt.pop()||S.expando+"_"+wt.guid++;return this[e]=!0,e}}),S.ajaxPrefilter("json jsonp",function(e,t,n){var r,i,o,a=!1!==e.jsonp&&(Ut.test(e.url)?"url":"string"==typeof e.data&&0===(e.contentType||"").indexOf("application/x-www-form-urlencoded")&&Ut.test(e.data)&&"data");if(a||"jsonp"===e.dataTypes[0])return r=e.jsonpCallback=m(e.jsonpCallback)?e.jsonpCallback():e.jsonpCallback,a?e[a]=e[a].replace(Ut,"$1"+r):!1!==e.jsonp&&(e.url+=(Tt.test(e.url)?"&":"?")+e.jsonp+"="+r),e.converters["script json"]=function(){return o||S.error(r+" was not called"),o[0]},e.dataTypes[0]="json",i=C[r],C[r]=function(){o=arguments},n.always(function(){void 0===i?S(C).removeProp(r):C[r]=i,e[r]&&(e.jsonpCallback=t.jsonpCallback,zt.push(r)),o&&m(i)&&i(o[0]),o=i=void 0}),"script"}),y.createHTMLDocument=((_t=E.implementation.createHTMLDocument("").body).innerHTML="
",2===_t.childNodes.length),S.parseHTML=function(e,t,n){return"string"!=typeof e?[]:("boolean"==typeof t&&(n=t,t=!1),t||(y.createHTMLDocument?((r=(t=E.implementation.createHTMLDocument("")).createElement("base")).href=E.location.href,t.head.appendChild(r)):t=E),o=!n&&[],(i=N.exec(e))?[t.createElement(i[1])]:(i=xe([e],t,o),o&&o.length&&S(o).remove(),S.merge([],i.childNodes)));var r,i,o},S.fn.load=function(e,t,n){var r,i,o,a=this,s=e.indexOf(" ");return-1").append(S.parseHTML(e)).find(r):e)}).always(n&&function(e,t){a.each(function(){n.apply(this,o||[e.responseText,t,e])})}),this},S.expr.pseudos.animated=function(t){return S.grep(S.timers,function(e){return t===e.elem}).length},S.offset={setOffset:function(e,t,n){var r,i,o,a,s,u,l=S.css(e,"position"),c=S(e),f={};"static"===l&&(e.style.position="relative"),s=c.offset(),o=S.css(e,"top"),u=S.css(e,"left"),("absolute"===l||"fixed"===l)&&-1<(o+u).indexOf("auto")?(a=(r=c.position()).top,i=r.left):(a=parseFloat(o)||0,i=parseFloat(u)||0),m(t)&&(t=t.call(e,n,S.extend({},s))),null!=t.top&&(f.top=t.top-s.top+a),null!=t.left&&(f.left=t.left-s.left+i),"using"in t?t.using.call(e,f):c.css(f)}},S.fn.extend({offset:function(t){if(arguments.length)return void 0===t?this:this.each(function(e){S.offset.setOffset(this,t,e)});var e,n,r=this[0];return r?r.getClientRects().length?(e=r.getBoundingClientRect(),n=r.ownerDocument.defaultView,{top:e.top+n.pageYOffset,left:e.left+n.pageXOffset}):{top:0,left:0}:void 0},position:function(){if(this[0]){var e,t,n,r=this[0],i={top:0,left:0};if("fixed"===S.css(r,"position"))t=r.getBoundingClientRect();else{t=this.offset(),n=r.ownerDocument,e=r.offsetParent||n.documentElement;while(e&&(e===n.body||e===n.documentElement)&&"static"===S.css(e,"position"))e=e.parentNode;e&&e!==r&&1===e.nodeType&&((i=S(e).offset()).top+=S.css(e,"borderTopWidth",!0),i.left+=S.css(e,"borderLeftWidth",!0))}return{top:t.top-i.top-S.css(r,"marginTop",!0),left:t.left-i.left-S.css(r,"marginLeft",!0)}}},offsetParent:function(){return this.map(function(){var e=this.offsetParent;while(e&&"static"===S.css(e,"position"))e=e.offsetParent;return e||re})}}),S.each({scrollLeft:"pageXOffset",scrollTop:"pageYOffset"},function(t,i){var o="pageYOffset"===i;S.fn[t]=function(e){return $(this,function(e,t,n){var r;if(x(e)?r=e:9===e.nodeType&&(r=e.defaultView),void 0===n)return r?r[i]:e[t];r?r.scrollTo(o?r.pageXOffset:n,o?n:r.pageYOffset):e[t]=n},t,e,arguments.length)}}),S.each(["top","left"],function(e,n){S.cssHooks[n]=Fe(y.pixelPosition,function(e,t){if(t)return t=We(e,n),Pe.test(t)?S(e).position()[n]+"px":t})}),S.each({Height:"height",Width:"width"},function(a,s){S.each({padding:"inner"+a,content:s,"":"outer"+a},function(r,o){S.fn[o]=function(e,t){var n=arguments.length&&(r||"boolean"!=typeof e),i=r||(!0===e||!0===t?"margin":"border");return $(this,function(e,t,n){var r;return x(e)?0===o.indexOf("outer")?e["inner"+a]:e.document.documentElement["client"+a]:9===e.nodeType?(r=e.documentElement,Math.max(e.body["scroll"+a],r["scroll"+a],e.body["offset"+a],r["offset"+a],r["client"+a])):void 0===n?S.css(e,t,i):S.style(e,t,n,i)},s,n?e:void 0,n)}})}),S.each(["ajaxStart","ajaxStop","ajaxComplete","ajaxError","ajaxSuccess","ajaxSend"],function(e,t){S.fn[t]=function(e){return this.on(t,e)}}),S.fn.extend({bind:function(e,t,n){return this.on(e,null,t,n)},unbind:function(e,t){return this.off(e,null,t)},delegate:function(e,t,n,r){return this.on(t,e,n,r)},undelegate:function(e,t,n){return 1===arguments.length?this.off(e,"**"):this.off(t,e||"**",n)},hover:function(e,t){return this.mouseenter(e).mouseleave(t||e)}}),S.each("blur focus focusin focusout resize scroll click dblclick mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave change select submit keydown keypress keyup contextmenu".split(" "),function(e,n){S.fn[n]=function(e,t){return 0
'};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;};NProgress.status=null;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;queue(function(next){if(Settings.positionUsing==='')Settings.positionUsing=NProgress.getPositioningCSS();css(bar,barPositionCSS(n,speed,ease));if(n===1){css(progress,{transition:'none',opacity:1});progress.offsetWidth;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';};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;};NProgress.done=function(force){if(!force&&!NProgress.status)return this;return NProgress.inc(0.3+0.5*Math.random()).set(1);};NProgress.inc=function(amount){var n=NProgress.status;if(!n){return NProgress.start();}else if(n>1){}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();};(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;};})();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;};NProgress.remove=function(){removeClass(document.documentElement,'nprogress-busy');removeClass(document.querySelector(Settings.parent),'nprogress-custom-parent');var progress=document.getElementById('nprogress');progress&&removeElement(progress);};NProgress.isRendered=function(){return!!document.getElementById('nprogress');};NProgress.getPositioningCSS=function(){var bodyStyle=document.body.style;var vendorPrefix=('WebkitTransform'in bodyStyle)?'Webkit':('MozTransform'in bodyStyle)?'Moz':('msTransform'in bodyStyle)?'ms':('OTransform'in bodyStyle)?'O':'';if(vendorPrefix+'Perspective'in bodyStyle){return'translate3d';}else if(vendorPrefix+'Transform'in bodyStyle){return'translate';}else{return'margin';}};function clamp(n,min,max){if(nmax)return max;return n;} +function toBarPerc(n){return(-1+n)*100;} +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;} +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();};})();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]);}}})();function hasClass(element,name){var list=typeof element=='string'?element:classList(element);return list.indexOf(' '+name+' ')>=0;} +function addClass(element,name){var oldList=classList(element),newList=oldList+name;if(hasClass(oldList,name))return;element.className=newList.substring(1);} +function removeClass(element,name){var oldList=classList(element),newList;if(!hasClass(element,name))return;newList=oldList.replace(' '+name+' ',' ');element.className=newList.substring(1,newList.length-1);} +function classList(element){return(' '+(element&&element.className||'')+' ').replace(/\s+/gi,' ');} +function removeElement(element){element&&element.parentNode&&element.parentNode.removeChild(element);} +return NProgress;});;function do_reply(parentid){console.log(parentid);$("#id_parent_comment_id").val(parentid) +$("#commentform").appendTo($("#div-comment-"+parentid));$("#reply-title").hide();$("#cancel_comment").show();} +function cancel_reply(){$("#reply-title").show();$("#cancel_comment").hide();$("#id_parent_comment_id").val('') +$("#commentform").appendTo($("#respond"));} +NProgress.start();NProgress.set(0.4);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);});window.onload=function(){var replyLinks=document.querySelectorAll(".comment-reply-link");for(var i=0;i a, .page_item_has_children > a',function(e){var el=$(this).parent('li');if(!el.hasClass('focus')){e.preventDefault();el.toggleClass('focus');el.siblings('.focus').removeClass('focus');}});}})(jQuery);;$(function(){MathJax.Hub.Config({showProcessingMessages:false,messageStyle:"none",extensions:["tex2jax.js"],jax:["input/TeX","output/HTML-CSS"],displayAlign:"left",tex2jax:{inlineMath:[["$","$"]],displayMath:[["$$","$$"]],skipTags:['script','noscript','style','textarea','pre','code','a'],},"HTML-CSS":{availableFonts:["STIX","TeX"],showMathMenu:false}});const contentId=document.getElementById("content");const commentId=document.getElementById("comments");MathJax.Hub.Queue(["Typeset",MathJax.Hub,contentId,commentId]);}); \ No newline at end of file diff --git a/src/DjangoBlog/collectedstatic/CACHE/js/output.de188198a436.js b/src/DjangoBlog/collectedstatic/CACHE/js/output.de188198a436.js new file mode 100644 index 0000000..02b1c1d --- /dev/null +++ b/src/DjangoBlog/collectedstatic/CACHE/js/output.de188198a436.js @@ -0,0 +1,26 @@ +/*! + * IE10 viewport hack for Surface/desktop Windows 8 bug + * Copyright 2014-2015 Twitter, Inc. + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) + */(function(){'use strict';if(navigator.userAgent.match(/IEMobile\/10\.0/)){var msViewportStyle=document.createElement('style') +msViewportStyle.appendChild(document.createTextNode('@-ms-viewport{width:auto!important}')) +document.querySelector('head').appendChild(msViewportStyle)}})();;/*! + * Copyright 2014-2015 Twitter, Inc. + * + * Licensed under the Creative Commons Attribution 3.0 Unported License. For + * details, see https://creativecommons.org/licenses/by/3.0/. + */(function(){'use strict';function emulatedIEMajorVersion(){var groups=/MSIE ([0-9.]+)/.exec(window.navigator.userAgent) +if(groups===null){return null} +var ieVersionNum=parseInt(groups[1],10) +var ieMajorVersion=Math.floor(ieVersionNum) +return ieMajorVersion} +function actualNonEmulatedIEMajorVersion(){var jscriptVersion=new Function('/*@cc_on return @_jscript_version; @*/')() +if(jscriptVersion===undefined){return 11} +if(jscriptVersion<9){return 8} +return jscriptVersion} +var ua=window.navigator.userAgent +if(ua.indexOf('Opera')>-1||ua.indexOf('Presto')>-1){return} +var emulated=emulatedIEMajorVersion() +if(emulated===null){return} +var nonEmulated=actualNonEmulatedIEMajorVersion() +if(emulated!==nonEmulated){window.alert('WARNING: You appear to be using IE'+nonEmulated+' in IE'+emulated+' emulation mode.\nIE emulation modes can behave significantly differently from ACTUAL older versions of IE.\nPLEASE DON\'T FILE BOOTSTRAP BUGS based on testing in IE emulation modes!')}})();; \ No newline at end of file diff --git a/src/comments/__init__.py b/src/DjangoBlog/comments/__init__.py similarity index 100% rename from src/comments/__init__.py rename to src/DjangoBlog/comments/__init__.py diff --git a/src/DjangoBlog/comments/__pycache__/__init__.cpython-310.pyc b/src/DjangoBlog/comments/__pycache__/__init__.cpython-310.pyc new file mode 100644 index 0000000..a5091e1 Binary files /dev/null and b/src/DjangoBlog/comments/__pycache__/__init__.cpython-310.pyc differ diff --git a/src/DjangoBlog/comments/__pycache__/admin.cpython-310.pyc b/src/DjangoBlog/comments/__pycache__/admin.cpython-310.pyc new file mode 100644 index 0000000..cff94fe Binary files /dev/null and b/src/DjangoBlog/comments/__pycache__/admin.cpython-310.pyc differ diff --git a/src/DjangoBlog/comments/__pycache__/apps.cpython-310.pyc b/src/DjangoBlog/comments/__pycache__/apps.cpython-310.pyc new file mode 100644 index 0000000..2de1010 Binary files /dev/null and b/src/DjangoBlog/comments/__pycache__/apps.cpython-310.pyc differ diff --git a/src/DjangoBlog/comments/__pycache__/forms.cpython-310.pyc b/src/DjangoBlog/comments/__pycache__/forms.cpython-310.pyc new file mode 100644 index 0000000..b1ef493 Binary files /dev/null and b/src/DjangoBlog/comments/__pycache__/forms.cpython-310.pyc differ diff --git a/src/DjangoBlog/comments/__pycache__/models.cpython-310.pyc b/src/DjangoBlog/comments/__pycache__/models.cpython-310.pyc new file mode 100644 index 0000000..0b4d532 Binary files /dev/null and b/src/DjangoBlog/comments/__pycache__/models.cpython-310.pyc differ diff --git a/src/DjangoBlog/comments/__pycache__/urls.cpython-310.pyc b/src/DjangoBlog/comments/__pycache__/urls.cpython-310.pyc new file mode 100644 index 0000000..71749e3 Binary files /dev/null and b/src/DjangoBlog/comments/__pycache__/urls.cpython-310.pyc differ diff --git a/src/DjangoBlog/comments/__pycache__/utils.cpython-310.pyc b/src/DjangoBlog/comments/__pycache__/utils.cpython-310.pyc new file mode 100644 index 0000000..983c68c Binary files /dev/null and b/src/DjangoBlog/comments/__pycache__/utils.cpython-310.pyc differ diff --git a/src/DjangoBlog/comments/__pycache__/views.cpython-310.pyc b/src/DjangoBlog/comments/__pycache__/views.cpython-310.pyc new file mode 100644 index 0000000..04109bf Binary files /dev/null and b/src/DjangoBlog/comments/__pycache__/views.cpython-310.pyc differ diff --git a/src/DjangoBlog/comments/admin.py b/src/DjangoBlog/comments/admin.py new file mode 100644 index 0000000..a814f3f --- /dev/null +++ b/src/DjangoBlog/comments/admin.py @@ -0,0 +1,47 @@ +from django.contrib import admin +from django.urls import reverse +from django.utils.html import format_html +from django.utils.translation import gettext_lazy as _ + + +def disable_commentstatus(modeladmin, request, queryset): + queryset.update(is_enable=False) + + +def enable_commentstatus(modeladmin, request, queryset): + queryset.update(is_enable=True) + + +disable_commentstatus.short_description = _('Disable comments') +enable_commentstatus.short_description = _('Enable comments') + + +class CommentAdmin(admin.ModelAdmin): + list_per_page = 20 + list_display = ( + 'id', + 'body', + 'link_to_userinfo', + 'link_to_article', + 'is_enable', + 'creation_time') + list_display_links = ('id', 'body', 'is_enable') + list_filter = ('is_enable',) + exclude = ('creation_time', 'last_modify_time') + actions = [disable_commentstatus, enable_commentstatus] + + def link_to_userinfo(self, obj): + info = (obj.author._meta.app_label, obj.author._meta.model_name) + link = reverse('admin:%s_%s_change' % info, args=(obj.author.id,)) + return format_html( + u'%s' % + (link, obj.author.nickname if obj.author.nickname else obj.author.email)) + + def link_to_article(self, obj): + info = (obj.article._meta.app_label, obj.article._meta.model_name) + link = reverse('admin:%s_%s_change' % info, args=(obj.article.id,)) + return format_html( + u'%s' % (link, obj.article.title)) + + link_to_userinfo.short_description = _('User') + link_to_article.short_description = _('Article') diff --git a/src/comments/apps.py b/src/DjangoBlog/comments/apps.py similarity index 100% rename from src/comments/apps.py rename to src/DjangoBlog/comments/apps.py diff --git a/src/comments/forms.py b/src/DjangoBlog/comments/forms.py similarity index 100% rename from src/comments/forms.py rename to src/DjangoBlog/comments/forms.py diff --git a/src/comments/migrations/0001_initial.py b/src/DjangoBlog/comments/migrations/0001_initial.py similarity index 100% rename from src/comments/migrations/0001_initial.py rename to src/DjangoBlog/comments/migrations/0001_initial.py diff --git a/src/comments/migrations/0002_alter_comment_is_enable.py b/src/DjangoBlog/comments/migrations/0002_alter_comment_is_enable.py similarity index 100% rename from src/comments/migrations/0002_alter_comment_is_enable.py rename to src/DjangoBlog/comments/migrations/0002_alter_comment_is_enable.py diff --git a/src/comments/migrations/0003_alter_comment_options_remove_comment_created_time_and_more.py b/src/DjangoBlog/comments/migrations/0003_alter_comment_options_remove_comment_created_time_and_more.py similarity index 100% rename from src/comments/migrations/0003_alter_comment_options_remove_comment_created_time_and_more.py rename to src/DjangoBlog/comments/migrations/0003_alter_comment_options_remove_comment_created_time_and_more.py diff --git a/src/comments/migrations/__init__.py b/src/DjangoBlog/comments/migrations/__init__.py similarity index 100% rename from src/comments/migrations/__init__.py rename to src/DjangoBlog/comments/migrations/__init__.py diff --git a/src/DjangoBlog/comments/migrations/__pycache__/0001_initial.cpython-310.pyc b/src/DjangoBlog/comments/migrations/__pycache__/0001_initial.cpython-310.pyc new file mode 100644 index 0000000..9b86df1 Binary files /dev/null and b/src/DjangoBlog/comments/migrations/__pycache__/0001_initial.cpython-310.pyc differ diff --git a/src/DjangoBlog/comments/migrations/__pycache__/0002_alter_comment_is_enable.cpython-310.pyc b/src/DjangoBlog/comments/migrations/__pycache__/0002_alter_comment_is_enable.cpython-310.pyc new file mode 100644 index 0000000..8ea8f65 Binary files /dev/null and b/src/DjangoBlog/comments/migrations/__pycache__/0002_alter_comment_is_enable.cpython-310.pyc differ diff --git a/src/DjangoBlog/comments/migrations/__pycache__/0003_alter_comment_options_remove_comment_created_time_and_more.cpython-310.pyc b/src/DjangoBlog/comments/migrations/__pycache__/0003_alter_comment_options_remove_comment_created_time_and_more.cpython-310.pyc new file mode 100644 index 0000000..af5d6f1 Binary files /dev/null and b/src/DjangoBlog/comments/migrations/__pycache__/0003_alter_comment_options_remove_comment_created_time_and_more.cpython-310.pyc differ diff --git a/src/DjangoBlog/comments/migrations/__pycache__/__init__.cpython-310.pyc b/src/DjangoBlog/comments/migrations/__pycache__/__init__.cpython-310.pyc new file mode 100644 index 0000000..824c212 Binary files /dev/null and b/src/DjangoBlog/comments/migrations/__pycache__/__init__.cpython-310.pyc differ diff --git a/src/comments/models.py b/src/DjangoBlog/comments/models.py similarity index 100% rename from src/comments/models.py rename to src/DjangoBlog/comments/models.py diff --git a/src/comments/templatetags/__init__.py b/src/DjangoBlog/comments/templatetags/__init__.py similarity index 100% rename from src/comments/templatetags/__init__.py rename to src/DjangoBlog/comments/templatetags/__init__.py diff --git a/src/DjangoBlog/comments/templatetags/__pycache__/__init__.cpython-310.pyc b/src/DjangoBlog/comments/templatetags/__pycache__/__init__.cpython-310.pyc new file mode 100644 index 0000000..d395816 Binary files /dev/null and b/src/DjangoBlog/comments/templatetags/__pycache__/__init__.cpython-310.pyc differ diff --git a/src/DjangoBlog/comments/templatetags/__pycache__/comments_tags.cpython-310.pyc b/src/DjangoBlog/comments/templatetags/__pycache__/comments_tags.cpython-310.pyc new file mode 100644 index 0000000..e134d85 Binary files /dev/null and b/src/DjangoBlog/comments/templatetags/__pycache__/comments_tags.cpython-310.pyc differ diff --git a/src/comments/templatetags/comments_tags.py b/src/DjangoBlog/comments/templatetags/comments_tags.py similarity index 100% rename from src/comments/templatetags/comments_tags.py rename to src/DjangoBlog/comments/templatetags/comments_tags.py diff --git a/src/comments/tests.py b/src/DjangoBlog/comments/tests.py similarity index 100% rename from src/comments/tests.py rename to src/DjangoBlog/comments/tests.py diff --git a/src/comments/urls.py b/src/DjangoBlog/comments/urls.py similarity index 100% rename from src/comments/urls.py rename to src/DjangoBlog/comments/urls.py diff --git a/src/comments/utils.py b/src/DjangoBlog/comments/utils.py similarity index 56% rename from src/comments/utils.py rename to src/DjangoBlog/comments/utils.py index 3cc8782..f01dba7 100644 --- a/src/comments/utils.py +++ b/src/DjangoBlog/comments/utils.py @@ -1,27 +1,17 @@ import logging -from django.utils.translation import gettext_lazy as _ # 用于支持多语言翻译 -from djangoblog.utils import get_current_site # 获取当前站点的工具函数 -from djangoblog.utils import send_email # 发送邮件的工具函数 +from django.utils.translation import gettext_lazy as _ + +from djangoblog.utils import get_current_site +from djangoblog.utils import send_email + +logger = logging.getLogger(__name__) -logger = logging.getLogger(__name__) # 初始化日志记录器,用于记录错误或调试信息 def send_comment_email(comment): - """ - 功能:发送评论相关的通知邮件。 - 参数: - comment: 当前的评论对象,包含评论内容、作者信息、文章信息等。 - """ - # 获取当前站点的域名 site = get_current_site().domain - - # 定义邮件主题 - subject = _('Thanks for your comment') # 邮件主题,支持多语言 - - # 构造文章的绝对 URL + subject = _('Thanks for your comment') article_url = f"https://{site}{comment.article.get_absolute_url()}" - - # 构造邮件内容(HTML 格式),感谢用户的评论 html_content = _("""

Thank you very much for your comments on this site

You can visit %(article_title)s to review your comments, @@ -29,17 +19,10 @@ def send_comment_email(comment):
If the link above cannot be opened, please copy this link to your browser. %(article_url)s""") % {'article_url': article_url, 'article_title': comment.article.title} - - # 获取评论作者的邮箱 tomail = comment.author.email - - # 发送邮件给评论作者 send_email([tomail], subject, html_content) - try: - # 如果当前评论是对其他评论的回复 if comment.parent_comment: - # 构造回复通知邮件内容 html_content = _("""Your comment on %(article_title)s
has received a reply.
%(comment_body)s
@@ -49,12 +32,7 @@ def send_comment_email(comment): %(article_url)s """) % {'article_url': article_url, 'article_title': comment.article.title, 'comment_body': comment.parent_comment.body} - - # 获取被回复评论作者的邮箱 tomail = comment.parent_comment.author.email - - # 发送邮件给被回复的评论作者 send_email([tomail], subject, html_content) except Exception as e: - # 如果发送邮件时发生异常,记录错误日志 - logger.error(e) \ No newline at end of file + logger.error(e) diff --git a/src/DjangoBlog/comments/views.py b/src/DjangoBlog/comments/views.py new file mode 100644 index 0000000..ad9b2b9 --- /dev/null +++ b/src/DjangoBlog/comments/views.py @@ -0,0 +1,63 @@ +# Create your views here. +from django.core.exceptions import ValidationError +from django.http import HttpResponseRedirect +from django.shortcuts import get_object_or_404 +from django.utils.decorators import method_decorator +from django.views.decorators.csrf import csrf_protect +from django.views.generic.edit import FormView + +from accounts.models import BlogUser +from blog.models import Article +from .forms import CommentForm +from .models import Comment + + +class CommentPostView(FormView): + form_class = CommentForm + template_name = 'blog/article_detail.html' + + @method_decorator(csrf_protect) + def dispatch(self, *args, **kwargs): + return super(CommentPostView, self).dispatch(*args, **kwargs) + + def get(self, request, *args, **kwargs): + article_id = self.kwargs['article_id'] + article = get_object_or_404(Article, pk=article_id) + url = article.get_absolute_url() + return HttpResponseRedirect(url + "#comments") + + def form_invalid(self, form): + article_id = self.kwargs['article_id'] + article = get_object_or_404(Article, pk=article_id) + + return self.render_to_response({ + 'form': form, + 'article': article + }) + + def form_valid(self, form): + """提交的数据验证合法后的逻辑""" + user = self.request.user + author = BlogUser.objects.get(pk=user.pk) + article_id = self.kwargs['article_id'] + article = get_object_or_404(Article, pk=article_id) + + if article.comment_status == 'c' or article.status == 'c': + raise ValidationError("该文章评论已关闭.") + comment = form.save(False) + comment.article = article + from djangoblog.utils import get_blog_setting + settings = get_blog_setting() + if not settings.comment_need_review: + comment.is_enable = True + comment.author = author + + if form.cleaned_data['parent_comment_id']: + parent_comment = Comment.objects.get( + pk=form.cleaned_data['parent_comment_id']) + comment.parent_comment = parent_comment + + comment.save(True) + return HttpResponseRedirect( + "%s#div-comment-%d" % + (article.get_absolute_url(), comment.pk)) diff --git a/src/deploy/docker-compose/docker-compose.es.yml b/src/DjangoBlog/deploy/docker-compose/docker-compose.es.yml similarity index 100% rename from src/deploy/docker-compose/docker-compose.es.yml rename to src/DjangoBlog/deploy/docker-compose/docker-compose.es.yml diff --git a/src/deploy/docker-compose/docker-compose.yml b/src/DjangoBlog/deploy/docker-compose/docker-compose.yml similarity index 100% rename from src/deploy/docker-compose/docker-compose.yml rename to src/DjangoBlog/deploy/docker-compose/docker-compose.yml diff --git a/src/deploy/entrypoint.sh b/src/DjangoBlog/deploy/entrypoint.sh similarity index 100% rename from src/deploy/entrypoint.sh rename to src/DjangoBlog/deploy/entrypoint.sh diff --git a/src/deploy/k8s/configmap.yaml b/src/DjangoBlog/deploy/k8s/configmap.yaml similarity index 100% rename from src/deploy/k8s/configmap.yaml rename to src/DjangoBlog/deploy/k8s/configmap.yaml diff --git a/src/deploy/k8s/deployment.yaml b/src/DjangoBlog/deploy/k8s/deployment.yaml similarity index 99% rename from src/deploy/k8s/deployment.yaml rename to src/DjangoBlog/deploy/k8s/deployment.yaml index b50c411..414fdcc 100644 --- a/src/deploy/k8s/deployment.yaml +++ b/src/DjangoBlog/deploy/k8s/deployment.yaml @@ -26,13 +26,13 @@ spec: name: djangoblog-env readinessProbe: httpGet: - path: /health/ + path: / port: 8000 initialDelaySeconds: 10 periodSeconds: 30 livenessProbe: httpGet: - path: /health/ + path: / port: 8000 initialDelaySeconds: 10 periodSeconds: 30 diff --git a/src/deploy/k8s/gateway.yaml b/src/DjangoBlog/deploy/k8s/gateway.yaml similarity index 100% rename from src/deploy/k8s/gateway.yaml rename to src/DjangoBlog/deploy/k8s/gateway.yaml diff --git a/src/deploy/k8s/pv.yaml b/src/DjangoBlog/deploy/k8s/pv.yaml similarity index 100% rename from src/deploy/k8s/pv.yaml rename to src/DjangoBlog/deploy/k8s/pv.yaml diff --git a/src/deploy/k8s/pvc.yaml b/src/DjangoBlog/deploy/k8s/pvc.yaml similarity index 100% rename from src/deploy/k8s/pvc.yaml rename to src/DjangoBlog/deploy/k8s/pvc.yaml diff --git a/src/deploy/k8s/service.yaml b/src/DjangoBlog/deploy/k8s/service.yaml similarity index 100% rename from src/deploy/k8s/service.yaml rename to src/DjangoBlog/deploy/k8s/service.yaml diff --git a/src/deploy/k8s/storageclass.yaml b/src/DjangoBlog/deploy/k8s/storageclass.yaml similarity index 100% rename from src/deploy/k8s/storageclass.yaml rename to src/DjangoBlog/deploy/k8s/storageclass.yaml diff --git a/src/deploy/nginx.conf b/src/DjangoBlog/deploy/nginx.conf similarity index 100% rename from src/deploy/nginx.conf rename to src/DjangoBlog/deploy/nginx.conf diff --git a/src/DjangoBlog/djangoblog/__init__.py b/src/DjangoBlog/djangoblog/__init__.py new file mode 100644 index 0000000..1e205f4 --- /dev/null +++ b/src/DjangoBlog/djangoblog/__init__.py @@ -0,0 +1 @@ +default_app_config = 'djangoblog.apps.DjangoblogAppConfig' diff --git a/src/DjangoBlog/djangoblog/__pycache__/__init__.cpython-310.pyc b/src/DjangoBlog/djangoblog/__pycache__/__init__.cpython-310.pyc new file mode 100644 index 0000000..762e8a3 Binary files /dev/null and b/src/DjangoBlog/djangoblog/__pycache__/__init__.cpython-310.pyc differ diff --git a/src/DjangoBlog/djangoblog/__pycache__/admin_site.cpython-310.pyc b/src/DjangoBlog/djangoblog/__pycache__/admin_site.cpython-310.pyc new file mode 100644 index 0000000..65d8998 Binary files /dev/null and b/src/DjangoBlog/djangoblog/__pycache__/admin_site.cpython-310.pyc differ diff --git a/src/DjangoBlog/djangoblog/__pycache__/apps.cpython-310.pyc b/src/DjangoBlog/djangoblog/__pycache__/apps.cpython-310.pyc new file mode 100644 index 0000000..23dab63 Binary files /dev/null and b/src/DjangoBlog/djangoblog/__pycache__/apps.cpython-310.pyc differ diff --git a/src/DjangoBlog/djangoblog/__pycache__/blog_signals.cpython-310.pyc b/src/DjangoBlog/djangoblog/__pycache__/blog_signals.cpython-310.pyc new file mode 100644 index 0000000..05ab4aa Binary files /dev/null and b/src/DjangoBlog/djangoblog/__pycache__/blog_signals.cpython-310.pyc differ diff --git a/src/DjangoBlog/djangoblog/__pycache__/elasticsearch_backend.cpython-310.pyc b/src/DjangoBlog/djangoblog/__pycache__/elasticsearch_backend.cpython-310.pyc new file mode 100644 index 0000000..80bd855 Binary files /dev/null and b/src/DjangoBlog/djangoblog/__pycache__/elasticsearch_backend.cpython-310.pyc differ diff --git a/src/DjangoBlog/djangoblog/__pycache__/feeds.cpython-310.pyc b/src/DjangoBlog/djangoblog/__pycache__/feeds.cpython-310.pyc new file mode 100644 index 0000000..2717c47 Binary files /dev/null and b/src/DjangoBlog/djangoblog/__pycache__/feeds.cpython-310.pyc differ diff --git a/src/DjangoBlog/djangoblog/__pycache__/logentryadmin.cpython-310.pyc b/src/DjangoBlog/djangoblog/__pycache__/logentryadmin.cpython-310.pyc new file mode 100644 index 0000000..ddcab0f Binary files /dev/null and b/src/DjangoBlog/djangoblog/__pycache__/logentryadmin.cpython-310.pyc differ diff --git a/src/DjangoBlog/djangoblog/__pycache__/settings.cpython-310.pyc b/src/DjangoBlog/djangoblog/__pycache__/settings.cpython-310.pyc new file mode 100644 index 0000000..1d4cf81 Binary files /dev/null and b/src/DjangoBlog/djangoblog/__pycache__/settings.cpython-310.pyc differ diff --git a/src/DjangoBlog/djangoblog/__pycache__/sitemap.cpython-310.pyc b/src/DjangoBlog/djangoblog/__pycache__/sitemap.cpython-310.pyc new file mode 100644 index 0000000..e819f52 Binary files /dev/null and b/src/DjangoBlog/djangoblog/__pycache__/sitemap.cpython-310.pyc differ diff --git a/src/DjangoBlog/djangoblog/__pycache__/spider_notify.cpython-310.pyc b/src/DjangoBlog/djangoblog/__pycache__/spider_notify.cpython-310.pyc new file mode 100644 index 0000000..717f6a0 Binary files /dev/null and b/src/DjangoBlog/djangoblog/__pycache__/spider_notify.cpython-310.pyc differ diff --git a/src/DjangoBlog/djangoblog/__pycache__/urls.cpython-310.pyc b/src/DjangoBlog/djangoblog/__pycache__/urls.cpython-310.pyc new file mode 100644 index 0000000..d881117 Binary files /dev/null and b/src/DjangoBlog/djangoblog/__pycache__/urls.cpython-310.pyc differ diff --git a/src/DjangoBlog/djangoblog/__pycache__/utils.cpython-310.pyc b/src/DjangoBlog/djangoblog/__pycache__/utils.cpython-310.pyc new file mode 100644 index 0000000..0272b22 Binary files /dev/null and b/src/DjangoBlog/djangoblog/__pycache__/utils.cpython-310.pyc differ diff --git a/src/DjangoBlog/djangoblog/__pycache__/whoosh_cn_backend.cpython-310.pyc b/src/DjangoBlog/djangoblog/__pycache__/whoosh_cn_backend.cpython-310.pyc new file mode 100644 index 0000000..7e5fbe8 Binary files /dev/null and b/src/DjangoBlog/djangoblog/__pycache__/whoosh_cn_backend.cpython-310.pyc differ diff --git a/src/DjangoBlog/djangoblog/__pycache__/wsgi.cpython-310.pyc b/src/DjangoBlog/djangoblog/__pycache__/wsgi.cpython-310.pyc new file mode 100644 index 0000000..8c89925 Binary files /dev/null and b/src/DjangoBlog/djangoblog/__pycache__/wsgi.cpython-310.pyc differ diff --git a/src/DjangoBlog/djangoblog/admin_site.py b/src/DjangoBlog/djangoblog/admin_site.py new file mode 100644 index 0000000..f120405 --- /dev/null +++ b/src/DjangoBlog/djangoblog/admin_site.py @@ -0,0 +1,64 @@ +from django.contrib.admin import AdminSite +from django.contrib.admin.models import LogEntry +from django.contrib.sites.admin import SiteAdmin +from django.contrib.sites.models import Site + +from accounts.admin import * +from blog.admin import * +from blog.models import * +from comments.admin import * +from comments.models import * +from djangoblog.logentryadmin import LogEntryAdmin +from oauth.admin import * +from oauth.models import * +from owntracks.admin import * +from owntracks.models import * +from servermanager.admin import * +from servermanager.models import * + + +class DjangoBlogAdminSite(AdminSite): + site_header = 'djangoblog administration' + site_title = 'djangoblog site admin' + + def __init__(self, name='admin'): + super().__init__(name) + + def has_permission(self, request): + return request.user.is_superuser + + # def get_urls(self): + # urls = super().get_urls() + # from django.urls import path + # from blog.views import refresh_memcache + # + # my_urls = [ + # path('refresh/', self.admin_view(refresh_memcache), name="refresh"), + # ] + # return urls + my_urls + + +admin_site = DjangoBlogAdminSite(name='admin') + +admin_site.register(Article, ArticlelAdmin) +admin_site.register(Category, CategoryAdmin) +admin_site.register(Tag, TagAdmin) +admin_site.register(Links, LinksAdmin) +admin_site.register(SideBar, SideBarAdmin) +admin_site.register(BlogSettings, BlogSettingsAdmin) + +admin_site.register(commands, CommandsAdmin) +admin_site.register(EmailSendLog, EmailSendLogAdmin) + +admin_site.register(BlogUser, BlogUserAdmin) + +admin_site.register(Comment, CommentAdmin) + +admin_site.register(OAuthUser, OAuthUserAdmin) +admin_site.register(OAuthConfig, OAuthConfigAdmin) + +admin_site.register(OwnTrackLog, OwnTrackLogsAdmin) + +admin_site.register(Site, SiteAdmin) + +admin_site.register(LogEntry, LogEntryAdmin) diff --git a/src/DjangoBlog/djangoblog/apps.py b/src/DjangoBlog/djangoblog/apps.py new file mode 100644 index 0000000..d29e318 --- /dev/null +++ b/src/DjangoBlog/djangoblog/apps.py @@ -0,0 +1,11 @@ +from django.apps import AppConfig + +class DjangoblogAppConfig(AppConfig): + default_auto_field = 'django.db.models.BigAutoField' + name = 'djangoblog' + + def ready(self): + super().ready() + # Import and load plugins here + from .plugin_manage.loader import load_plugins + load_plugins() \ No newline at end of file diff --git a/src/DjangoBlog/djangoblog/blog_signals.py b/src/DjangoBlog/djangoblog/blog_signals.py new file mode 100644 index 0000000..393f441 --- /dev/null +++ b/src/DjangoBlog/djangoblog/blog_signals.py @@ -0,0 +1,122 @@ +import _thread +import logging + +import django.dispatch +from django.conf import settings +from django.contrib.admin.models import LogEntry +from django.contrib.auth.signals import user_logged_in, user_logged_out +from django.core.mail import EmailMultiAlternatives +from django.db.models.signals import post_save +from django.dispatch import receiver + +from comments.models import Comment +from comments.utils import send_comment_email +from djangoblog.spider_notify import SpiderNotify +from djangoblog.utils import cache, expire_view_cache, delete_sidebar_cache, delete_view_cache +from djangoblog.utils import get_current_site +from oauth.models import OAuthUser + +logger = logging.getLogger(__name__) + +oauth_user_login_signal = django.dispatch.Signal(['id']) +send_email_signal = django.dispatch.Signal( + ['emailto', 'title', 'content']) + + +@receiver(send_email_signal) +def send_email_signal_handler(sender, **kwargs): + emailto = kwargs['emailto'] + title = kwargs['title'] + content = kwargs['content'] + + msg = EmailMultiAlternatives( + title, + content, + from_email=settings.DEFAULT_FROM_EMAIL, + to=emailto) + msg.content_subtype = "html" + + from servermanager.models import EmailSendLog + log = EmailSendLog() + log.title = title + log.content = content + log.emailto = ','.join(emailto) + + try: + result = msg.send() + log.send_result = result > 0 + except Exception as e: + logger.error(f"失败邮箱号: {emailto}, {e}") + log.send_result = False + log.save() + + +@receiver(oauth_user_login_signal) +def oauth_user_login_signal_handler(sender, **kwargs): + id = kwargs['id'] + oauthuser = OAuthUser.objects.get(id=id) + site = get_current_site().domain + if oauthuser.picture and not oauthuser.picture.find(site) >= 0: + from djangoblog.utils import save_user_avatar + oauthuser.picture = save_user_avatar(oauthuser.picture) + oauthuser.save() + + delete_sidebar_cache() + + +@receiver(post_save) +def model_post_save_callback( + sender, + instance, + created, + raw, + using, + update_fields, + **kwargs): + clearcache = False + if isinstance(instance, LogEntry): + return + if 'get_full_url' in dir(instance): + is_update_views = update_fields == {'views'} + if not settings.TESTING and not is_update_views: + try: + notify_url = instance.get_full_url() + SpiderNotify.baidu_notify([notify_url]) + except Exception as ex: + logger.error("notify sipder", ex) + if not is_update_views: + clearcache = True + + if isinstance(instance, Comment): + if instance.is_enable: + path = instance.article.get_absolute_url() + site = get_current_site().domain + if site.find(':') > 0: + site = site[0:site.find(':')] + + expire_view_cache( + path, + servername=site, + serverport=80, + key_prefix='blogdetail') + if cache.get('seo_processor'): + cache.delete('seo_processor') + comment_cache_key = 'article_comments_{id}'.format( + id=instance.article.id) + cache.delete(comment_cache_key) + delete_sidebar_cache() + delete_view_cache('article_comments', [str(instance.article.pk)]) + + _thread.start_new_thread(send_comment_email, (instance,)) + + if clearcache: + cache.clear() + + +@receiver(user_logged_in) +@receiver(user_logged_out) +def user_auth_callback(sender, request, user, **kwargs): + if user and user.username: + logger.info(user) + delete_sidebar_cache() + # cache.clear() diff --git a/src/DjangoBlog/djangoblog/elasticsearch_backend.py b/src/DjangoBlog/djangoblog/elasticsearch_backend.py new file mode 100644 index 0000000..4afe498 --- /dev/null +++ b/src/DjangoBlog/djangoblog/elasticsearch_backend.py @@ -0,0 +1,183 @@ +from django.utils.encoding import force_str +from elasticsearch_dsl import Q +from haystack.backends import BaseEngine, BaseSearchBackend, BaseSearchQuery, log_query +from haystack.forms import ModelSearchForm +from haystack.models import SearchResult +from haystack.utils import log as logging + +from blog.documents import ArticleDocument, ArticleDocumentManager +from blog.models import Article + +logger = logging.getLogger(__name__) + + +class ElasticSearchBackend(BaseSearchBackend): + def __init__(self, connection_alias, **connection_options): + super( + ElasticSearchBackend, + self).__init__( + connection_alias, + **connection_options) + self.manager = ArticleDocumentManager() + self.include_spelling = True + + def _get_models(self, iterable): + models = iterable if iterable and iterable[0] else Article.objects.all() + docs = self.manager.convert_to_doc(models) + return docs + + def _create(self, models): + self.manager.create_index() + docs = self._get_models(models) + self.manager.rebuild(docs) + + def _delete(self, models): + for m in models: + m.delete() + return True + + def _rebuild(self, models): + models = models if models else Article.objects.all() + docs = self.manager.convert_to_doc(models) + self.manager.update_docs(docs) + + def update(self, index, iterable, commit=True): + + models = self._get_models(iterable) + self.manager.update_docs(models) + + def remove(self, obj_or_string): + models = self._get_models([obj_or_string]) + self._delete(models) + + def clear(self, models=None, commit=True): + self.remove(None) + + @staticmethod + def get_suggestion(query: str) -> str: + """获取推荐词, 如果没有找到添加原搜索词""" + + search = ArticleDocument.search() \ + .query("match", body=query) \ + .suggest('suggest_search', query, term={'field': 'body'}) \ + .execute() + + keywords = [] + for suggest in search.suggest.suggest_search: + if suggest["options"]: + keywords.append(suggest["options"][0]["text"]) + else: + keywords.append(suggest["text"]) + + return ' '.join(keywords) + + @log_query + def search(self, query_string, **kwargs): + logger.info('search query_string:' + query_string) + + start_offset = kwargs.get('start_offset') + end_offset = kwargs.get('end_offset') + + # 推荐词搜索 + if getattr(self, "is_suggest", None): + suggestion = self.get_suggestion(query_string) + else: + suggestion = query_string + + q = Q('bool', + should=[Q('match', body=suggestion), Q('match', title=suggestion)], + minimum_should_match="70%") + + search = ArticleDocument.search() \ + .query('bool', filter=[q]) \ + .filter('term', status='p') \ + .filter('term', type='a') \ + .source(False)[start_offset: end_offset] + + results = search.execute() + hits = results['hits'].total + raw_results = [] + for raw_result in results['hits']['hits']: + app_label = 'blog' + model_name = 'Article' + additional_fields = {} + + result_class = SearchResult + + result = result_class( + app_label, + model_name, + raw_result['_id'], + raw_result['_score'], + **additional_fields) + raw_results.append(result) + facets = {} + spelling_suggestion = None if query_string == suggestion else suggestion + + return { + 'results': raw_results, + 'hits': hits, + 'facets': facets, + 'spelling_suggestion': spelling_suggestion, + } + + +class ElasticSearchQuery(BaseSearchQuery): + def _convert_datetime(self, date): + if hasattr(date, 'hour'): + return force_str(date.strftime('%Y%m%d%H%M%S')) + else: + return force_str(date.strftime('%Y%m%d000000')) + + def clean(self, query_fragment): + """ + Provides a mechanism for sanitizing user input before presenting the + value to the backend. + + Whoosh 1.X differs here in that you can no longer use a backslash + to escape reserved characters. Instead, the whole word should be + quoted. + """ + words = query_fragment.split() + cleaned_words = [] + + for word in words: + if word in self.backend.RESERVED_WORDS: + word = word.replace(word, word.lower()) + + for char in self.backend.RESERVED_CHARACTERS: + if char in word: + word = "'%s'" % word + break + + cleaned_words.append(word) + + return ' '.join(cleaned_words) + + def build_query_fragment(self, field, filter_type, value): + return value.query_string + + def get_count(self): + results = self.get_results() + return len(results) if results else 0 + + def get_spelling_suggestion(self, preferred_query=None): + return self._spelling_suggestion + + def build_params(self, spelling_query=None): + kwargs = super(ElasticSearchQuery, self).build_params(spelling_query=spelling_query) + return kwargs + + +class ElasticSearchModelSearchForm(ModelSearchForm): + + def search(self): + # 是否建议搜索 + self.searchqueryset.query.backend.is_suggest = self.data.get("is_suggest") != "no" + sqs = super().search() + return sqs + + +class ElasticSearchEngine(BaseEngine): + backend = ElasticSearchBackend + query = ElasticSearchQuery diff --git a/src/DjangoBlog/djangoblog/feeds.py b/src/DjangoBlog/djangoblog/feeds.py new file mode 100644 index 0000000..8c4e851 --- /dev/null +++ b/src/DjangoBlog/djangoblog/feeds.py @@ -0,0 +1,40 @@ +from django.contrib.auth import get_user_model +from django.contrib.syndication.views import Feed +from django.utils import timezone +from django.utils.feedgenerator import Rss201rev2Feed + +from blog.models import Article +from djangoblog.utils import CommonMarkdown + + +class DjangoBlogFeed(Feed): + feed_type = Rss201rev2Feed + + description = '大巧无工,重剑无锋.' + title = "且听风吟 大巧无工,重剑无锋. " + link = "/feed/" + + def author_name(self): + return get_user_model().objects.first().nickname + + def author_link(self): + return get_user_model().objects.first().get_absolute_url() + + def items(self): + return Article.objects.filter(type='a', status='p').order_by('-pub_time')[:5] + + def item_title(self, item): + return item.title + + def item_description(self, item): + return CommonMarkdown.get_markdown(item.body) + + def feed_copyright(self): + now = timezone.now() + return "Copyright© {year} 且听风吟".format(year=now.year) + + def item_link(self, item): + return item.get_absolute_url() + + def item_guid(self, item): + return diff --git a/src/DjangoBlog/djangoblog/logentryadmin.py b/src/DjangoBlog/djangoblog/logentryadmin.py new file mode 100644 index 0000000..2f6a535 --- /dev/null +++ b/src/DjangoBlog/djangoblog/logentryadmin.py @@ -0,0 +1,91 @@ +from django.contrib import admin +from django.contrib.admin.models import DELETION +from django.contrib.contenttypes.models import ContentType +from django.urls import reverse, NoReverseMatch +from django.utils.encoding import force_str +from django.utils.html import escape +from django.utils.safestring import mark_safe +from django.utils.translation import gettext_lazy as _ + + +class LogEntryAdmin(admin.ModelAdmin): + list_filter = [ + 'content_type' + ] + + search_fields = [ + 'object_repr', + 'change_message' + ] + + list_display_links = [ + 'action_time', + 'get_change_message', + ] + list_display = [ + 'action_time', + 'user_link', + 'content_type', + 'object_link', + 'get_change_message', + ] + + def has_add_permission(self, request): + return False + + def has_change_permission(self, request, obj=None): + return ( + request.user.is_superuser or + request.user.has_perm('admin.change_logentry') + ) and request.method != 'POST' + + def has_delete_permission(self, request, obj=None): + return False + + def object_link(self, obj): + object_link = escape(obj.object_repr) + content_type = obj.content_type + + if obj.action_flag != DELETION and content_type is not None: + # try returning an actual link instead of object repr string + try: + url = reverse( + 'admin:{}_{}_change'.format(content_type.app_label, + content_type.model), + args=[obj.object_id] + ) + object_link = '{}'.format(url, object_link) + except NoReverseMatch: + pass + return mark_safe(object_link) + + object_link.admin_order_field = 'object_repr' + object_link.short_description = _('object') + + def user_link(self, obj): + content_type = ContentType.objects.get_for_model(type(obj.user)) + user_link = escape(force_str(obj.user)) + try: + # try returning an actual link instead of object repr string + url = reverse( + 'admin:{}_{}_change'.format(content_type.app_label, + content_type.model), + args=[obj.user.pk] + ) + user_link = '{}'.format(url, user_link) + except NoReverseMatch: + pass + return mark_safe(user_link) + + user_link.admin_order_field = 'user' + user_link.short_description = _('user') + + def get_queryset(self, request): + queryset = super(LogEntryAdmin, self).get_queryset(request) + return queryset.prefetch_related('content_type') + + def get_actions(self, request): + actions = super(LogEntryAdmin, self).get_actions(request) + if 'delete_selected' in actions: + del actions['delete_selected'] + return actions diff --git a/src/DjangoBlog/djangoblog/plugin_manage/__pycache__/base_plugin.cpython-310.pyc b/src/DjangoBlog/djangoblog/plugin_manage/__pycache__/base_plugin.cpython-310.pyc new file mode 100644 index 0000000..5b1610e Binary files /dev/null and b/src/DjangoBlog/djangoblog/plugin_manage/__pycache__/base_plugin.cpython-310.pyc differ diff --git a/src/DjangoBlog/djangoblog/plugin_manage/__pycache__/hook_constants.cpython-310.pyc b/src/DjangoBlog/djangoblog/plugin_manage/__pycache__/hook_constants.cpython-310.pyc new file mode 100644 index 0000000..700e68a Binary files /dev/null and b/src/DjangoBlog/djangoblog/plugin_manage/__pycache__/hook_constants.cpython-310.pyc differ diff --git a/src/DjangoBlog/djangoblog/plugin_manage/__pycache__/hooks.cpython-310.pyc b/src/DjangoBlog/djangoblog/plugin_manage/__pycache__/hooks.cpython-310.pyc new file mode 100644 index 0000000..613144f Binary files /dev/null and b/src/DjangoBlog/djangoblog/plugin_manage/__pycache__/hooks.cpython-310.pyc differ diff --git a/src/DjangoBlog/djangoblog/plugin_manage/__pycache__/loader.cpython-310.pyc b/src/DjangoBlog/djangoblog/plugin_manage/__pycache__/loader.cpython-310.pyc new file mode 100644 index 0000000..775f80e Binary files /dev/null and b/src/DjangoBlog/djangoblog/plugin_manage/__pycache__/loader.cpython-310.pyc differ diff --git a/src/DjangoBlog/djangoblog/plugin_manage/base_plugin.py b/src/DjangoBlog/djangoblog/plugin_manage/base_plugin.py new file mode 100644 index 0000000..2b4be5c --- /dev/null +++ b/src/DjangoBlog/djangoblog/plugin_manage/base_plugin.py @@ -0,0 +1,41 @@ +import logging + +logger = logging.getLogger(__name__) + + +class BasePlugin: + # 插件元数据 + PLUGIN_NAME = None + PLUGIN_DESCRIPTION = None + PLUGIN_VERSION = None + + def __init__(self): + if not all([self.PLUGIN_NAME, self.PLUGIN_DESCRIPTION, self.PLUGIN_VERSION]): + raise ValueError("Plugin metadata (PLUGIN_NAME, PLUGIN_DESCRIPTION, PLUGIN_VERSION) must be defined.") + self.init_plugin() + self.register_hooks() + + def init_plugin(self): + """ + 插件初始化逻辑 + 子类可以重写此方法来实现特定的初始化操作 + """ + logger.info(f'{self.PLUGIN_NAME} initialized.') + + def register_hooks(self): + """ + 注册插件钩子 + 子类可以重写此方法来注册特定的钩子 + """ + pass + + def get_plugin_info(self): + """ + 获取插件信息 + :return: 包含插件元数据的字典 + """ + return { + 'name': self.PLUGIN_NAME, + 'description': self.PLUGIN_DESCRIPTION, + 'version': self.PLUGIN_VERSION + } diff --git a/src/DjangoBlog/djangoblog/plugin_manage/hook_constants.py b/src/DjangoBlog/djangoblog/plugin_manage/hook_constants.py new file mode 100644 index 0000000..6685b7c --- /dev/null +++ b/src/DjangoBlog/djangoblog/plugin_manage/hook_constants.py @@ -0,0 +1,7 @@ +ARTICLE_DETAIL_LOAD = 'article_detail_load' +ARTICLE_CREATE = 'article_create' +ARTICLE_UPDATE = 'article_update' +ARTICLE_DELETE = 'article_delete' + +ARTICLE_CONTENT_HOOK_NAME = "the_content" + diff --git a/src/DjangoBlog/djangoblog/plugin_manage/hooks.py b/src/DjangoBlog/djangoblog/plugin_manage/hooks.py new file mode 100644 index 0000000..d712540 --- /dev/null +++ b/src/DjangoBlog/djangoblog/plugin_manage/hooks.py @@ -0,0 +1,44 @@ +import logging + +logger = logging.getLogger(__name__) + +_hooks = {} + + +def register(hook_name: str, callback: callable): + """ + 注册一个钩子回调。 + """ + if hook_name not in _hooks: + _hooks[hook_name] = [] + _hooks[hook_name].append(callback) + logger.debug(f"Registered hook '{hook_name}' with callback '{callback.__name__}'") + + +def run_action(hook_name: str, *args, **kwargs): + """ + 执行一个 Action Hook。 + 它会按顺序执行所有注册到该钩子上的回调函数。 + """ + if hook_name in _hooks: + logger.debug(f"Running action hook '{hook_name}'") + for callback in _hooks[hook_name]: + try: + callback(*args, **kwargs) + except Exception as e: + logger.error(f"Error running action hook '{hook_name}' callback '{callback.__name__}': {e}", exc_info=True) + + +def apply_filters(hook_name: str, value, *args, **kwargs): + """ + 执行一个 Filter Hook。 + 它会把 value 依次传递给所有注册的回调函数进行处理。 + """ + if hook_name in _hooks: + logger.debug(f"Applying filter hook '{hook_name}'") + for callback in _hooks[hook_name]: + try: + value = callback(value, *args, **kwargs) + except Exception as e: + logger.error(f"Error applying filter hook '{hook_name}' callback '{callback.__name__}': {e}", exc_info=True) + return value diff --git a/src/DjangoBlog/djangoblog/plugin_manage/loader.py b/src/DjangoBlog/djangoblog/plugin_manage/loader.py new file mode 100644 index 0000000..12e824b --- /dev/null +++ b/src/DjangoBlog/djangoblog/plugin_manage/loader.py @@ -0,0 +1,19 @@ +import os +import logging +from django.conf import settings + +logger = logging.getLogger(__name__) + +def load_plugins(): + """ + Dynamically loads and initializes plugins from the 'plugins' directory. + This function is intended to be called when the Django app registry is ready. + """ + for plugin_name in settings.ACTIVE_PLUGINS: + plugin_path = os.path.join(settings.PLUGINS_DIR, plugin_name) + if os.path.isdir(plugin_path) and os.path.exists(os.path.join(plugin_path, 'plugin.py')): + try: + __import__(f'plugins.{plugin_name}.plugin') + logger.info(f"Successfully loaded plugin: {plugin_name}") + except ImportError as e: + logger.error(f"Failed to import plugin: {plugin_name}", exc_info=e) \ No newline at end of file diff --git a/src/DjangoBlog/djangoblog/settings.py b/src/DjangoBlog/djangoblog/settings.py new file mode 100644 index 0000000..43cef6d --- /dev/null +++ b/src/DjangoBlog/djangoblog/settings.py @@ -0,0 +1,242 @@ +""" +Django settings for djangoblog project. +项目核心配置文件,包含数据库、中间件、应用、资源路径等关键运行参数 +Generated by 'django-admin startproject' using Django 1.10.2. +""" +import os +import sys +from pathlib import Path + +from django.utils.translation import gettext_lazy as _ + + +# 环境变量转布尔值工具函数:处理配置中需要布尔类型的环境变量 +def env_to_bool(env, default): + str_val = os.environ.get(env) + return default if str_val is None else str_val == 'True' + + +# 项目根目录:当前配置文件所在目录的父目录(即项目根路径) +BASE_DIR = Path(__file__).resolve().parent.parent + + +# -------------------------- 基础安全与运行配置 -------------------------- +# 密钥:生产环境需通过环境变量配置,默认值仅用于开发(避免硬编码泄露) +SECRET_KEY = os.environ.get('DJANGO_SECRET_KEY') or 'n9ceqv38)#&mwuat@(mjb_p%em$e8$qyr#fw9ot!=ba6lijx-6' +# 调试模式:开发环境True,生产环境必须False(通过环境变量控制) +DEBUG = env_to_bool('DJANGO_DEBUG', True) +# 测试模式标识:执行python manage.py test时为True +TESTING = len(sys.argv) > 1 and sys.argv[1] == 'test' + +# 允许访问的主机:生产环境需指定具体域名,*为开发环境临时配置 +ALLOWED_HOSTS = ['*', '127.0.0.1', 'example.com'] +# Django 4.0+ 新增:信任的CSRF来源,解决跨域CSRF验证问题 +CSRF_TRUSTED_ORIGINS = ['http://example.com'] + + +# -------------------------- 已安装应用 -------------------------- +INSTALLED_APPS = [ + 'django.contrib.admin.apps.SimpleAdminConfig', # 简化版Admin后台 + 'django.contrib.auth', 'django.contrib.contenttypes', 'django.contrib.sessions', + 'django.contrib.messages', 'django.contrib.staticfiles', # Django内置核心应用 + 'django.contrib.sites', 'django.contrib.sitemaps', # 站点管理、站点地图 + 'mdeditor', # Markdown编辑器(用于文章编辑) + 'haystack', # 全文搜索框架 + 'blog', 'accounts', 'comments', 'oauth', 'servermanager', 'owntracks', # 项目业务应用 + 'compressor', # 静态资源压缩工具 + 'djangoblog' # 项目主应用 +] + + +# -------------------------- 中间件配置 -------------------------- +MIDDLEWARE = [ + 'django.middleware.security.SecurityMiddleware', # 安全相关中间件 + 'django.contrib.sessions.middleware.SessionMiddleware', # 会话管理 + 'django.middleware.locale.LocaleMiddleware', # 国际化支持 + 'django.middleware.gzip.GZipMiddleware', # 响应GZip压缩 + 'django.middleware.common.CommonMiddleware', # 通用中间件 + 'django.middleware.csrf.CsrfViewMiddleware', # CSRF防护 + 'django.contrib.auth.middleware.AuthenticationMiddleware', # 用户认证 + 'django.contrib.messages.middleware.MessageMiddleware', # 消息提示 + 'django.middleware.clickjacking.XFrameOptionsMiddleware', # X-Frame-Options防护 + 'django.middleware.http.ConditionalGetMiddleware', # 条件请求优化 + 'blog.middleware.OnlineMiddleware' # 自定义:用户在线状态中间件 +] + + +# -------------------------- URL与模板配置 -------------------------- +ROOT_URLCONF = 'djangoblog.urls' # 主URL配置文件路径 + +TEMPLATES = [ + { + 'BACKEND': 'django.template.backends.django.DjangoTemplates', + 'DIRS': [os.path.join(BASE_DIR, 'templates')], # 全局模板目录 + 'APP_DIRS': True, # 允许加载各应用内的templates目录 + 'OPTIONS': { + 'context_processors': [ + 'django.template.context_processors.debug', + 'django.template.context_processors.request', + 'django.contrib.auth.context_processors.auth', + 'django.contrib.messages.context_processors.messages', + 'blog.context_processors.seo_processor' # 自定义:SEO相关上下文 + ], + }, + }, +] + +WSGI_APPLICATION = 'djangoblog.wsgi.application' # WSGI应用入口 + + +# -------------------------- 数据库配置 -------------------------- +DATABASES = { + 'default': { + 'ENGINE': 'django.db.backends.mysql', # 使用MySQL数据库 + 'NAME': os.environ.get('DJANGO_MYSQL_DATABASE') or 'djangoblog', # 数据库名 + 'USER': os.environ.get('DJANGO_MYSQL_USER') or 'root', # 数据库用户名 + 'PASSWORD': os.environ.get('DJANGO_MYSQL_PASSWORD') or '123456', # 密码 + 'HOST': os.environ.get('DJANGO_MYSQL_HOST') or '127.0.0.1', # 数据库地址 + 'PORT': int(os.environ.get('DJANGO_MYSQL_PORT') or 3306), # 端口 + 'OPTIONS': {'charset': 'utf8mb4'}, # 支持emoji的字符集 + }} + + +# -------------------------- 密码验证与用户配置 -------------------------- +AUTH_PASSWORD_VALIDATORS = [ # Django内置密码强度验证规则 + {'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator'}, + {'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator'}, + {'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator'}, + {'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator'}, +] + +AUTH_USER_MODEL = 'accounts.BlogUser' # 自定义用户模型(替换默认User) +LOGIN_URL = '/login/' # 用户登录跳转URL + + +# -------------------------- 国际化与时间配置 -------------------------- +LANGUAGES = (('en', _('English')), ('zh-hans', _('Simplified Chinese')), ('zh-hant', _('Traditional Chinese'))) +LOCALE_PATHS = (os.path.join(BASE_DIR, 'locale'),) # 国际化翻译文件目录 +LANGUAGE_CODE = 'zh-hans' # 默认语言:简体中文 +TIME_ZONE = 'Asia/Shanghai' # 时区:上海 +USE_I18N = True # 启用国际化 +USE_L10N = True # 启用本地化 +USE_TZ = False # 不使用UTC时间(使用本地时间) + + +# -------------------------- 静态资源与媒体文件 -------------------------- +STATIC_ROOT = os.path.join(BASE_DIR, 'collectedstatic') # 生产环境静态资源收集目录 +STATIC_URL = '/static/' # 静态资源访问URL前缀 +STATICFILES = os.path.join(BASE_DIR, 'static') # 开发环境静态资源目录 + +MEDIA_ROOT = os.path.join(BASE_DIR, 'uploads') # 上传文件存储目录 +MEDIA_URL = '/media/' # 上传文件访问URL前缀 + +# 静态资源压缩配置(compressor) +STATICFILES_FINDERS = ( + 'django.contrib.staticfiles.finders.FileSystemFinder', + 'django.contrib.staticfiles.finders.AppDirectoriesFinder', + 'compressor.finders.CompressorFinder', +) +COMPRESS_ENABLED = True # 启用压缩 +COMPRESS_CSS_FILTERS = ['compressor.filters.css_default.CssAbsoluteFilter', 'compressor.filters.cssmin.CSSMinFilter'] +COMPRESS_JS_FILTERS = ['compressor.filters.jsmin.JSMinFilter'] + + +# -------------------------- 全文搜索(Haystack)配置 -------------------------- +HAYSTACK_CONNECTIONS = { + 'default': { + 'ENGINE': 'djangoblog.whoosh_cn_backend.WhooshEngine', # 中文Whoosh引擎 + 'PATH': os.path.join(os.path.dirname(__file__), 'whoosh_index'), # 索引存储路径 + }, +} +HAYSTACK_SIGNAL_PROCESSOR = 'haystack.signals.RealtimeSignalProcessor' # 实时更新索引 + +# 生产环境支持Elasticsearch(通过环境变量启用) +if os.environ.get('DJANGO_ELASTICSEARCH_HOST'): + ELASTICSEARCH_DSL = {'default': {'hosts': os.environ.get('DJANGO_ELASTICSEARCH_HOST')}} + HAYSTACK_CONNECTIONS = {'default': {'ENGINE': 'djangoblog.elasticsearch_backend.ElasticSearchEngine'}} + + +# -------------------------- 缓存配置 -------------------------- +CACHES = { + 'default': { + 'BACKEND': 'django.core.cache.backends.locmem.LocMemCache', # 默认:内存缓存 + 'TIMEOUT': 10800, # 缓存超时时间(秒) + 'LOCATION': 'unique-snowflake', + } +} +# 生产环境支持Redis缓存(通过环境变量启用) +if os.environ.get("DJANGO_REDIS_URL"): + CACHES = {'default': {'BACKEND': 'django.core.cache.backends.redis.RedisCache', 'LOCATION': f'redis://{os.environ.get("DJANGO_REDIS_URL")}'}} + +CACHE_CONTROL_MAX_AGE = 2592000 # HTTP缓存最大有效期(秒) + + +# -------------------------- 日志配置 -------------------------- +LOG_PATH = os.path.join(BASE_DIR, 'logs') # 日志存储目录 +if not os.path.exists(LOG_PATH): + os.makedirs(LOG_PATH, exist_ok=True) + +LOGGING = { + 'version': 1, + 'disable_existing_loggers': False, + 'root': {'level': 'INFO', 'handlers': ['console', 'log_file']}, + 'formatters': {'verbose': {'format': '[%(asctime)s] %(levelname)s [%(name)s.%(funcName)s:%(lineno)d %(module)s] %(message)s'}}, + 'filters': {'require_debug_false': {'()': 'django.utils.log.RequireDebugFalse'}, 'require_debug_true': {'()': 'django.utils.log.RequireDebugTrue'}}, + 'handlers': { + 'log_file': {'level': 'INFO', 'class': 'logging.handlers.TimedRotatingFileHandler', 'filename': os.path.join(LOG_PATH, 'djangoblog.log'), 'when': 'D', 'formatter': 'verbose', 'interval': 1, 'delay': True, 'backupCount': 5, 'encoding': 'utf-8'}, + 'console': {'level': 'DEBUG', 'filters': ['require_debug_true'], 'class': 'logging.StreamHandler', 'formatter': 'verbose'}, + 'null': {'class': 'logging.NullHandler'}, + 'mail_admins': {'level': 'ERROR', 'filters': ['require_debug_false'], 'class': 'django.utils.log.AdminEmailHandler'} + }, + 'loggers': { + 'djangoblog': {'handlers': ['log_file', 'console'], 'level': 'INFO', 'propagate': True}, + 'django.request': {'handlers': ['mail_admins'], 'level': 'ERROR', 'propagate': False} + } +} + + +# -------------------------- 项目业务配置 -------------------------- +SITE_ID = 1 # 站点ID(用于sitemap等功能) +BAIDU_NOTIFY_URL = os.environ.get('DJANGO_BAIDU_NOTIFY_URL') or 'http://data.zz.baidu.com/urls?site=https://www.lylinux.net&token=1uAOGrMsUm5syDGn' # 百度收录通知URL + +# 时间格式定义 +TIME_FORMAT = '%Y-%m-%d %H:%M:%S' +DATE_TIME_FORMAT = '%Y-%m-%d' + +# Bootstrap样式类型(用于标签、按钮等样式随机选择) +BOOTSTRAP_COLOR_TYPES = ['default', 'primary', 'success', 'info', 'warning', 'danger'] + +# 分页配置:每页显示10条 +PAGINATE_BY = 10 + +# 邮件配置(用于管理员通知、用户验证等) +EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend' +EMAIL_USE_TLS = env_to_bool('DJANGO_EMAIL_TLS', False) +EMAIL_USE_SSL = env_to_bool('DJANGO_EMAIL_SSL', True) +EMAIL_HOST = os.environ.get('DJANGO_EMAIL_HOST') or 'smtp.mxhichina.com' +EMAIL_PORT = int(os.environ.get('DJANGO_EMAIL_PORT') or 465) +EMAIL_HOST_USER = os.environ.get('DJANGO_EMAIL_USER') +EMAIL_HOST_PASSWORD = os.environ.get('DJANGO_EMAIL_PASSWORD') +DEFAULT_FROM_EMAIL = EMAIL_HOST_USER +SERVER_EMAIL = EMAIL_HOST_USER +ADMINS = [('admin', os.environ.get('DJANGO_ADMIN_EMAIL') or 'admin@admin.com')] # 管理员邮箱(接收错误通知) + +# 微信管理员密码(二次MD5加密) +WXADMIN = os.environ.get('DJANGO_WXADMIN_PASSWORD') or '995F03AC401D6CABABAEF756FC4D43C7' + +# X-Frame-Options配置:允许同源页面嵌入iframe +X_FRAME_OPTIONS = 'SAMEORIGIN' + +# 默认自增字段类型(Django 3.2+ 推荐) +DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField' + + +# -------------------------- 插件系统配置 -------------------------- +PLUGINS_DIR = BASE_DIR / 'plugins' # 插件目录 +ACTIVE_PLUGINS = [ # 启用的插件列表 + 'article_copyright', # 文章版权信息 + 'reading_time', # 文章阅读时长 + 'external_links', # 外部链接处理 + 'view_count', # 文章阅读量统计 + 'seo_optimizer' # SEO优化 +] \ No newline at end of file diff --git a/src/DjangoBlog/djangoblog/sitemap.py b/src/DjangoBlog/djangoblog/sitemap.py new file mode 100644 index 0000000..8b7d446 --- /dev/null +++ b/src/DjangoBlog/djangoblog/sitemap.py @@ -0,0 +1,59 @@ +from django.contrib.sitemaps import Sitemap +from django.urls import reverse + +from blog.models import Article, Category, Tag + + +class StaticViewSitemap(Sitemap): + priority = 0.5 + changefreq = 'daily' + + def items(self): + return ['blog:index', ] + + def location(self, item): + return reverse(item) + + +class ArticleSiteMap(Sitemap): + changefreq = "monthly" + priority = "0.6" + + def items(self): + return Article.objects.filter(status='p') + + def lastmod(self, obj): + return obj.last_modify_time + + +class CategorySiteMap(Sitemap): + changefreq = "Weekly" + priority = "0.6" + + def items(self): + return Category.objects.all() + + def lastmod(self, obj): + return obj.last_modify_time + + +class TagSiteMap(Sitemap): + changefreq = "Weekly" + priority = "0.3" + + def items(self): + return Tag.objects.all() + + def lastmod(self, obj): + return obj.last_modify_time + + +class UserSiteMap(Sitemap): + changefreq = "Weekly" + priority = "0.3" + + def items(self): + return list(set(map(lambda x: x.author, Article.objects.all()))) + + def lastmod(self, obj): + return obj.date_joined diff --git a/src/DjangoBlog/djangoblog/spider_notify.py b/src/DjangoBlog/djangoblog/spider_notify.py new file mode 100644 index 0000000..7b909e9 --- /dev/null +++ b/src/DjangoBlog/djangoblog/spider_notify.py @@ -0,0 +1,21 @@ +import logging + +import requests +from django.conf import settings + +logger = logging.getLogger(__name__) + + +class SpiderNotify(): + @staticmethod + def baidu_notify(urls): + try: + data = '\n'.join(urls) + result = requests.post(settings.BAIDU_NOTIFY_URL, data=data) + logger.info(result.text) + except Exception as e: + logger.error(e) + + @staticmethod + def notify(url): + SpiderNotify.baidu_notify(url) diff --git a/src/DjangoBlog/djangoblog/tests.py b/src/DjangoBlog/djangoblog/tests.py new file mode 100644 index 0000000..01237d9 --- /dev/null +++ b/src/DjangoBlog/djangoblog/tests.py @@ -0,0 +1,32 @@ +from django.test import TestCase + +from djangoblog.utils import * + + +class DjangoBlogTest(TestCase): + def setUp(self): + pass + + def test_utils(self): + md5 = get_sha256('test') + self.assertIsNotNone(md5) + c = CommonMarkdown.get_markdown(''' + # Title1 + + ```python + import os + ``` + + [url](https://www.lylinux.net/) + + [ddd](http://www.baidu.com) + + + ''') + self.assertIsNotNone(c) + d = { + 'd': 'key1', + 'd2': 'key2' + } + data = parse_dict_to_url(d) + self.assertIsNotNone(data) diff --git a/src/DjangoBlog/djangoblog/urls.py b/src/DjangoBlog/djangoblog/urls.py new file mode 100644 index 0000000..4aae58a --- /dev/null +++ b/src/DjangoBlog/djangoblog/urls.py @@ -0,0 +1,64 @@ +"""djangoblog URL Configuration + +The `urlpatterns` list routes URLs to views. For more information please see: + https://docs.djangoproject.com/en/1.10/topics/http/urls/ +Examples: +Function views + 1. Add an import: from my_app import views + 2. Add a URL to urlpatterns: url(r'^$', views.home, name='home') +Class-based views + 1. Add an import: from other_app.views import Home + 2. Add a URL to urlpatterns: url(r'^$', Home.as_view(), name='home') +Including another URLconf + 1. Import the include() function: from django.conf.urls import url, include + 2. Add a URL to urlpatterns: url(r'^blog/', include('blog.urls')) +""" +from django.conf import settings +from django.conf.urls.i18n import i18n_patterns +from django.conf.urls.static import static +from django.contrib.sitemaps.views import sitemap +from django.urls import path, include +from django.urls import re_path +from haystack.views import search_view_factory + +from blog.views import EsSearchView +from djangoblog.admin_site import admin_site +from djangoblog.elasticsearch_backend import ElasticSearchModelSearchForm +from djangoblog.feeds import DjangoBlogFeed +from djangoblog.sitemap import ArticleSiteMap, CategorySiteMap, StaticViewSitemap, TagSiteMap, UserSiteMap + +sitemaps = { + + 'blog': ArticleSiteMap, + 'Category': CategorySiteMap, + 'Tag': TagSiteMap, + 'User': UserSiteMap, + 'static': StaticViewSitemap +} + +handler404 = 'blog.views.page_not_found_view' +handler500 = 'blog.views.server_error_view' +handle403 = 'blog.views.permission_denied_view' + +urlpatterns = [ + path('i18n/', include('django.conf.urls.i18n')), +] +urlpatterns += i18n_patterns( + re_path(r'^admin/', admin_site.urls), + re_path(r'', include('blog.urls', namespace='blog')), + re_path(r'mdeditor/', include('mdeditor.urls')), + re_path(r'', include('comments.urls', namespace='comment')), + re_path(r'', include('accounts.urls', namespace='account')), + re_path(r'', include('oauth.urls', namespace='oauth')), + re_path(r'^sitemap\.xml$', sitemap, {'sitemaps': sitemaps}, + name='django.contrib.sitemaps.views.sitemap'), + re_path(r'^feed/$', DjangoBlogFeed()), + re_path(r'^rss/$', DjangoBlogFeed()), + re_path('^search', search_view_factory(view_class=EsSearchView, form_class=ElasticSearchModelSearchForm), + name='search'), + re_path(r'', include('servermanager.urls', namespace='servermanager')), + re_path(r'', include('owntracks.urls', namespace='owntracks')) + , prefix_default_language=False) + static(settings.STATIC_URL, document_root=settings.STATIC_ROOT) +if settings.DEBUG: + urlpatterns += static(settings.MEDIA_URL, + document_root=settings.MEDIA_ROOT) diff --git a/src/DjangoBlog/djangoblog/utils.py b/src/DjangoBlog/djangoblog/utils.py new file mode 100644 index 0000000..57f63dc --- /dev/null +++ b/src/DjangoBlog/djangoblog/utils.py @@ -0,0 +1,232 @@ +#!/usr/bin/env python +# encoding: utf-8 + + +import logging +import os +import random +import string +import uuid +from hashlib import sha256 + +import bleach +import markdown +import requests +from django.conf import settings +from django.contrib.sites.models import Site +from django.core.cache import cache +from django.templatetags.static import static + +logger = logging.getLogger(__name__) + + +def get_max_articleid_commentid(): + from blog.models import Article + from comments.models import Comment + return (Article.objects.latest().pk, Comment.objects.latest().pk) + + +def get_sha256(str): + m = sha256(str.encode('utf-8')) + return m.hexdigest() + + +def cache_decorator(expiration=3 * 60): + def wrapper(func): + def news(*args, **kwargs): + try: + view = args[0] + key = view.get_cache_key() + except: + key = None + if not key: + unique_str = repr((func, args, kwargs)) + + m = sha256(unique_str.encode('utf-8')) + key = m.hexdigest() + value = cache.get(key) + if value is not None: + # logger.info('cache_decorator get cache:%s key:%s' % (func.__name__, key)) + if str(value) == '__default_cache_value__': + return None + else: + return value + else: + logger.debug( + 'cache_decorator set cache:%s key:%s' % + (func.__name__, key)) + value = func(*args, **kwargs) + if value is None: + cache.set(key, '__default_cache_value__', expiration) + else: + cache.set(key, value, expiration) + return value + + return news + + return wrapper + + +def expire_view_cache(path, servername, serverport, key_prefix=None): + ''' + 刷新视图缓存 + :param path:url路径 + :param servername:host + :param serverport:端口 + :param key_prefix:前缀 + :return:是否成功 + ''' + from django.http import HttpRequest + from django.utils.cache import get_cache_key + + request = HttpRequest() + request.META = {'SERVER_NAME': servername, 'SERVER_PORT': serverport} + request.path = path + + key = get_cache_key(request, key_prefix=key_prefix, cache=cache) + if key: + logger.info('expire_view_cache:get key:{path}'.format(path=path)) + if cache.get(key): + cache.delete(key) + return True + return False + + +@cache_decorator() +def get_current_site(): + site = Site.objects.get_current() + return site + + +class CommonMarkdown: + @staticmethod + def _convert_markdown(value): + md = markdown.Markdown( + extensions=[ + 'extra', + 'codehilite', + 'toc', + 'tables', + ] + ) + body = md.convert(value) + toc = md.toc + return body, toc + + @staticmethod + def get_markdown_with_toc(value): + body, toc = CommonMarkdown._convert_markdown(value) + return body, toc + + @staticmethod + def get_markdown(value): + body, toc = CommonMarkdown._convert_markdown(value) + return body + + +def send_email(emailto, title, content): + from djangoblog.blog_signals import send_email_signal + send_email_signal.send( + send_email.__class__, + emailto=emailto, + title=title, + content=content) + + +def generate_code() -> str: + """生成随机数验证码""" + return ''.join(random.sample(string.digits, 6)) + + +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 + + +def get_blog_setting(): + value = cache.get('get_blog_setting') + if value: + return value + else: + from blog.models import BlogSettings + if not BlogSettings.objects.count(): + setting = BlogSettings() + setting.site_name = 'djangoblog' + setting.site_description = '基于Django的博客系统' + setting.site_seo_description = '基于Django的博客系统' + setting.site_keywords = 'Django,Python' + setting.article_sub_length = 300 + setting.sidebar_article_count = 10 + setting.sidebar_comment_count = 5 + setting.show_google_adsense = False + setting.open_site_comment = True + setting.analytics_code = '' + setting.beian_code = '' + setting.show_gongan_code = False + setting.comment_need_review = False + setting.save() + value = BlogSettings.objects.first() + logger.info('set cache get_blog_setting') + cache.set('get_blog_setting', value) + return value + + +def save_user_avatar(url): + ''' + 保存用户头像 + :param url:头像url + :return: 本地路径 + ''' + logger.info(url) + + try: + basedir = os.path.join(settings.STATICFILES, 'avatar') + rsp = requests.get(url, timeout=2) + if rsp.status_code == 200: + if not os.path.exists(basedir): + os.makedirs(basedir) + + image_extensions = ['.jpg', '.png', 'jpeg', '.gif'] + isimage = len([i for i in image_extensions if url.endswith(i)]) > 0 + ext = os.path.splitext(url)[1] if isimage else '.jpg' + save_filename = str(uuid.uuid4().hex) + ext + logger.info('保存用户头像:' + basedir + save_filename) + with open(os.path.join(basedir, save_filename), 'wb+') as file: + file.write(rsp.content) + return static('avatar/' + save_filename) + except Exception as e: + logger.error(e) + return static('blog/img/avatar.png') + + +def delete_sidebar_cache(): + from blog.models import LinkShowType + keys = ["sidebar" + x for x in LinkShowType.values] + for k in keys: + logger.info('delete sidebar key:' + k) + cache.delete(k) + + +def delete_view_cache(prefix, keys): + from django.core.cache.utils import make_template_fragment_key + key = make_template_fragment_key(prefix, keys) + cache.delete(key) + + +def get_resource_url(): + if settings.STATIC_URL: + return settings.STATIC_URL + else: + site = get_current_site() + return 'http://' + site.domain + '/static/' + + +ALLOWED_TAGS = ['a', 'abbr', 'acronym', 'b', 'blockquote', 'code', 'em', 'i', 'li', 'ol', 'pre', 'strong', 'ul', 'h1', + 'h2', 'p'] +ALLOWED_ATTRIBUTES = {'a': ['href', 'title'], 'abbr': ['title'], 'acronym': ['title']} + + +def sanitize_html(html): + return bleach.clean(html, tags=ALLOWED_TAGS, attributes=ALLOWED_ATTRIBUTES) diff --git a/src/DjangoBlog/djangoblog/whoosh_cn_backend.py b/src/DjangoBlog/djangoblog/whoosh_cn_backend.py new file mode 100644 index 0000000..04e3f7f --- /dev/null +++ b/src/DjangoBlog/djangoblog/whoosh_cn_backend.py @@ -0,0 +1,1044 @@ +# encoding: utf-8 + +from __future__ import absolute_import, division, print_function, unicode_literals + +import json +import os +import re +import shutil +import threading +import warnings + +import six +from django.conf import settings +from django.core.exceptions import ImproperlyConfigured +from datetime import datetime +from django.utils.encoding import force_str +from haystack.backends import BaseEngine, BaseSearchBackend, BaseSearchQuery, EmptyResults, log_query +from haystack.constants import DJANGO_CT, DJANGO_ID, ID +from haystack.exceptions import MissingDependency, SearchBackendError, SkipDocument +from haystack.inputs import Clean, Exact, PythonData, Raw +from haystack.models import SearchResult +from haystack.utils import get_identifier, get_model_ct +from haystack.utils import log as logging +from haystack.utils.app_loading import haystack_get_model +from jieba.analyse import ChineseAnalyzer +from whoosh import index +from whoosh.analysis import StemmingAnalyzer +from whoosh.fields import BOOLEAN, DATETIME, IDLIST, KEYWORD, NGRAM, NGRAMWORDS, NUMERIC, Schema, TEXT +from whoosh.fields import ID as WHOOSH_ID +from whoosh.filedb.filestore import FileStorage, RamStorage +from whoosh.highlight import ContextFragmenter, HtmlFormatter +from whoosh.highlight import highlight as whoosh_highlight +from whoosh.qparser import QueryParser +from whoosh.searching import ResultsPage +from whoosh.writing import AsyncWriter + +try: + import whoosh +except ImportError: + raise MissingDependency( + "The 'whoosh' backend requires the installation of 'Whoosh'. Please refer to the documentation.") + +# Handle minimum requirement. +if not hasattr(whoosh, '__version__') or whoosh.__version__ < (2, 5, 0): + raise MissingDependency( + "The 'whoosh' backend requires version 2.5.0 or greater.") + +# Bubble up the correct error. + +DATETIME_REGEX = re.compile( + '^(?P\d{4})-(?P\d{2})-(?P\d{2})T(?P\d{2}):(?P\d{2}):(?P\d{2})(\.\d{3,6}Z?)?$') +LOCALS = threading.local() +LOCALS.RAM_STORE = None + + +class WhooshHtmlFormatter(HtmlFormatter): + """ + This is a HtmlFormatter simpler than the whoosh.HtmlFormatter. + We use it to have consistent results across backends. Specifically, + Solr, Xapian and Elasticsearch are using this formatting. + """ + template = '<%(tag)s>%(t)s' + + +class WhooshSearchBackend(BaseSearchBackend): + # Word reserved by Whoosh for special use. + RESERVED_WORDS = ( + 'AND', + 'NOT', + 'OR', + 'TO', + ) + + # Characters reserved by Whoosh for special use. + # The '\\' must come first, so as not to overwrite the other slash + # replacements. + RESERVED_CHARACTERS = ( + '\\', '+', '-', '&&', '||', '!', '(', ')', '{', '}', + '[', ']', '^', '"', '~', '*', '?', ':', '.', + ) + + def __init__(self, connection_alias, **connection_options): + super( + WhooshSearchBackend, + self).__init__( + connection_alias, + **connection_options) + self.setup_complete = False + self.use_file_storage = True + self.post_limit = getattr( + connection_options, + 'POST_LIMIT', + 128 * 1024 * 1024) + self.path = connection_options.get('PATH') + + if connection_options.get('STORAGE', 'file') != 'file': + self.use_file_storage = False + + if self.use_file_storage and not self.path: + raise ImproperlyConfigured( + "You must specify a 'PATH' in your settings for connection '%s'." % + connection_alias) + + self.log = logging.getLogger('haystack') + + def setup(self): + """ + Defers loading until needed. + """ + from haystack import connections + new_index = False + + # Make sure the index is there. + if self.use_file_storage and not os.path.exists(self.path): + os.makedirs(self.path) + new_index = True + + if self.use_file_storage and not os.access(self.path, os.W_OK): + raise IOError( + "The path to your Whoosh index '%s' is not writable for the current user/group." % + self.path) + + if self.use_file_storage: + self.storage = FileStorage(self.path) + else: + global LOCALS + + if getattr(LOCALS, 'RAM_STORE', None) is None: + LOCALS.RAM_STORE = RamStorage() + + self.storage = LOCALS.RAM_STORE + + self.content_field_name, self.schema = self.build_schema( + connections[self.connection_alias].get_unified_index().all_searchfields()) + self.parser = QueryParser(self.content_field_name, schema=self.schema) + + if new_index is True: + self.index = self.storage.create_index(self.schema) + else: + try: + self.index = self.storage.open_index(schema=self.schema) + except index.EmptyIndexError: + self.index = self.storage.create_index(self.schema) + + self.setup_complete = True + + def build_schema(self, fields): + schema_fields = { + ID: WHOOSH_ID(stored=True, unique=True), + DJANGO_CT: WHOOSH_ID(stored=True), + DJANGO_ID: WHOOSH_ID(stored=True), + } + # Grab the number of keys that are hard-coded into Haystack. + # We'll use this to (possibly) fail slightly more gracefully later. + initial_key_count = len(schema_fields) + content_field_name = '' + + for field_name, field_class in fields.items(): + if field_class.is_multivalued: + if field_class.indexed is False: + schema_fields[field_class.index_fieldname] = IDLIST( + stored=True, field_boost=field_class.boost) + else: + schema_fields[field_class.index_fieldname] = KEYWORD( + stored=True, commas=True, scorable=True, field_boost=field_class.boost) + elif field_class.field_type in ['date', 'datetime']: + schema_fields[field_class.index_fieldname] = DATETIME( + stored=field_class.stored, sortable=True) + elif field_class.field_type == 'integer': + schema_fields[field_class.index_fieldname] = NUMERIC( + stored=field_class.stored, numtype=int, field_boost=field_class.boost) + elif field_class.field_type == 'float': + schema_fields[field_class.index_fieldname] = NUMERIC( + stored=field_class.stored, numtype=float, field_boost=field_class.boost) + elif field_class.field_type == 'boolean': + # Field boost isn't supported on BOOLEAN as of 1.8.2. + schema_fields[field_class.index_fieldname] = BOOLEAN( + stored=field_class.stored) + elif field_class.field_type == 'ngram': + schema_fields[field_class.index_fieldname] = NGRAM( + minsize=3, maxsize=15, stored=field_class.stored, field_boost=field_class.boost) + elif field_class.field_type == 'edge_ngram': + schema_fields[field_class.index_fieldname] = NGRAMWORDS(minsize=2, maxsize=15, at='start', + stored=field_class.stored, + field_boost=field_class.boost) + else: + # schema_fields[field_class.index_fieldname] = TEXT(stored=True, analyzer=StemmingAnalyzer(), field_boost=field_class.boost, sortable=True) + schema_fields[field_class.index_fieldname] = TEXT( + stored=True, analyzer=ChineseAnalyzer(), field_boost=field_class.boost, sortable=True) + if field_class.document is True: + content_field_name = field_class.index_fieldname + schema_fields[field_class.index_fieldname].spelling = True + + # Fail more gracefully than relying on the backend to die if no fields + # are found. + if len(schema_fields) <= initial_key_count: + raise SearchBackendError( + "No fields were found in any search_indexes. Please correct this before attempting to search.") + + return (content_field_name, Schema(**schema_fields)) + + def update(self, index, iterable, commit=True): + if not self.setup_complete: + self.setup() + + self.index = self.index.refresh() + writer = AsyncWriter(self.index) + + for obj in iterable: + try: + doc = index.full_prepare(obj) + except SkipDocument: + self.log.debug(u"Indexing for object `%s` skipped", obj) + else: + # Really make sure it's unicode, because Whoosh won't have it any + # other way. + for key in doc: + doc[key] = self._from_python(doc[key]) + + # Document boosts aren't supported in Whoosh 2.5.0+. + if 'boost' in doc: + del doc['boost'] + + try: + writer.update_document(**doc) + except Exception as e: + if not self.silently_fail: + raise + + # We'll log the object identifier but won't include the actual object + # to avoid the possibility of that generating encoding errors while + # processing the log message: + self.log.error( + u"%s while preparing object for update" % + e.__class__.__name__, + exc_info=True, + extra={ + "data": { + "index": index, + "object": get_identifier(obj)}}) + + if len(iterable) > 0: + # For now, commit no matter what, as we run into locking issues + # otherwise. + writer.commit() + + def remove(self, obj_or_string, commit=True): + if not self.setup_complete: + self.setup() + + self.index = self.index.refresh() + whoosh_id = get_identifier(obj_or_string) + + try: + self.index.delete_by_query( + q=self.parser.parse( + u'%s:"%s"' % + (ID, whoosh_id))) + except Exception as e: + if not self.silently_fail: + raise + + self.log.error( + "Failed to remove document '%s' from Whoosh: %s", + whoosh_id, + e, + exc_info=True) + + def clear(self, models=None, commit=True): + if not self.setup_complete: + self.setup() + + self.index = self.index.refresh() + + if models is not None: + assert isinstance(models, (list, tuple)) + + try: + if models is None: + self.delete_index() + else: + models_to_delete = [] + + for model in models: + models_to_delete.append( + u"%s:%s" % + (DJANGO_CT, get_model_ct(model))) + + self.index.delete_by_query( + q=self.parser.parse( + u" OR ".join(models_to_delete))) + except Exception as e: + if not self.silently_fail: + raise + + if models is not None: + self.log.error( + "Failed to clear Whoosh index of models '%s': %s", + ','.join(models_to_delete), + e, + exc_info=True) + else: + self.log.error( + "Failed to clear Whoosh index: %s", e, exc_info=True) + + def delete_index(self): + # Per the Whoosh mailing list, if wiping out everything from the index, + # it's much more efficient to simply delete the index files. + if self.use_file_storage and os.path.exists(self.path): + shutil.rmtree(self.path) + elif not self.use_file_storage: + self.storage.clean() + + # Recreate everything. + self.setup() + + def optimize(self): + if not self.setup_complete: + self.setup() + + self.index = self.index.refresh() + self.index.optimize() + + def calculate_page(self, start_offset=0, end_offset=None): + # Prevent against Whoosh throwing an error. Requires an end_offset + # greater than 0. + if end_offset is not None and end_offset <= 0: + end_offset = 1 + + # Determine the page. + page_num = 0 + + if end_offset is None: + end_offset = 1000000 + + if start_offset is None: + start_offset = 0 + + page_length = end_offset - start_offset + + if page_length and page_length > 0: + page_num = int(start_offset / page_length) + + # Increment because Whoosh uses 1-based page numbers. + page_num += 1 + return page_num, page_length + + @log_query + def search( + self, + query_string, + sort_by=None, + start_offset=0, + end_offset=None, + fields='', + highlight=False, + facets=None, + date_facets=None, + query_facets=None, + narrow_queries=None, + spelling_query=None, + within=None, + dwithin=None, + distance_point=None, + models=None, + limit_to_registered_models=None, + result_class=None, + **kwargs): + if not self.setup_complete: + self.setup() + + # A zero length query should return no results. + if len(query_string) == 0: + return { + 'results': [], + 'hits': 0, + } + + query_string = force_str(query_string) + + # A one-character query (non-wildcard) gets nabbed by a stopwords + # filter and should yield zero results. + if len(query_string) <= 1 and query_string != u'*': + return { + 'results': [], + 'hits': 0, + } + + reverse = False + + if sort_by is not None: + # Determine if we need to reverse the results and if Whoosh can + # handle what it's being asked to sort by. Reversing is an + # all-or-nothing action, unfortunately. + sort_by_list = [] + reverse_counter = 0 + + for order_by in sort_by: + if order_by.startswith('-'): + reverse_counter += 1 + + if reverse_counter and reverse_counter != len(sort_by): + raise SearchBackendError("Whoosh requires all order_by fields" + " to use the same sort direction") + + for order_by in sort_by: + if order_by.startswith('-'): + sort_by_list.append(order_by[1:]) + + if len(sort_by_list) == 1: + reverse = True + else: + sort_by_list.append(order_by) + + if len(sort_by_list) == 1: + reverse = False + + sort_by = sort_by_list[0] + + if facets is not None: + warnings.warn( + "Whoosh does not handle faceting.", + Warning, + stacklevel=2) + + if date_facets is not None: + warnings.warn( + "Whoosh does not handle date faceting.", + Warning, + stacklevel=2) + + if query_facets is not None: + warnings.warn( + "Whoosh does not handle query faceting.", + Warning, + stacklevel=2) + + narrowed_results = None + self.index = self.index.refresh() + + if limit_to_registered_models is None: + limit_to_registered_models = getattr( + settings, 'HAYSTACK_LIMIT_TO_REGISTERED_MODELS', True) + + if models and len(models): + model_choices = sorted(get_model_ct(model) for model in models) + elif limit_to_registered_models: + # Using narrow queries, limit the results to only models handled + # with the current routers. + model_choices = self.build_models_list() + else: + model_choices = [] + + if len(model_choices) > 0: + if narrow_queries is None: + narrow_queries = set() + + narrow_queries.add(' OR '.join( + ['%s:%s' % (DJANGO_CT, rm) for rm in model_choices])) + + narrow_searcher = None + + if narrow_queries is not None: + # Potentially expensive? I don't see another way to do it in + # Whoosh... + narrow_searcher = self.index.searcher() + + for nq in narrow_queries: + recent_narrowed_results = narrow_searcher.search( + self.parser.parse(force_str(nq)), limit=None) + + if len(recent_narrowed_results) <= 0: + return { + 'results': [], + 'hits': 0, + } + + if narrowed_results: + narrowed_results.filter(recent_narrowed_results) + else: + narrowed_results = recent_narrowed_results + + self.index = self.index.refresh() + + if self.index.doc_count(): + searcher = self.index.searcher() + parsed_query = self.parser.parse(query_string) + + # In the event of an invalid/stopworded query, recover gracefully. + if parsed_query is None: + return { + 'results': [], + 'hits': 0, + } + + page_num, page_length = self.calculate_page( + start_offset, end_offset) + + search_kwargs = { + 'pagelen': page_length, + 'sortedby': sort_by, + 'reverse': reverse, + } + + # Handle the case where the results have been narrowed. + if narrowed_results is not None: + search_kwargs['filter'] = narrowed_results + + try: + raw_page = searcher.search_page( + parsed_query, + page_num, + **search_kwargs + ) + except ValueError: + if not self.silently_fail: + raise + + return { + 'results': [], + 'hits': 0, + 'spelling_suggestion': None, + } + + # Because as of Whoosh 2.5.1, it will return the wrong page of + # results if you request something too high. :( + if raw_page.pagenum < page_num: + return { + 'results': [], + 'hits': 0, + 'spelling_suggestion': None, + } + + results = self._process_results( + raw_page, + highlight=highlight, + query_string=query_string, + spelling_query=spelling_query, + result_class=result_class) + searcher.close() + + if hasattr(narrow_searcher, 'close'): + narrow_searcher.close() + + return results + else: + if self.include_spelling: + if spelling_query: + spelling_suggestion = self.create_spelling_suggestion( + spelling_query) + else: + spelling_suggestion = self.create_spelling_suggestion( + query_string) + else: + spelling_suggestion = None + + return { + 'results': [], + 'hits': 0, + 'spelling_suggestion': spelling_suggestion, + } + + def more_like_this( + self, + model_instance, + additional_query_string=None, + start_offset=0, + end_offset=None, + models=None, + limit_to_registered_models=None, + result_class=None, + **kwargs): + if not self.setup_complete: + self.setup() + + # Deferred models will have a different class ("RealClass_Deferred_fieldname") + # which won't be in our registry: + model_klass = model_instance._meta.concrete_model + + field_name = self.content_field_name + narrow_queries = set() + narrowed_results = None + self.index = self.index.refresh() + + if limit_to_registered_models is None: + limit_to_registered_models = getattr( + settings, 'HAYSTACK_LIMIT_TO_REGISTERED_MODELS', True) + + if models and len(models): + model_choices = sorted(get_model_ct(model) for model in models) + elif limit_to_registered_models: + # Using narrow queries, limit the results to only models handled + # with the current routers. + model_choices = self.build_models_list() + else: + model_choices = [] + + if len(model_choices) > 0: + if narrow_queries is None: + narrow_queries = set() + + narrow_queries.add(' OR '.join( + ['%s:%s' % (DJANGO_CT, rm) for rm in model_choices])) + + if additional_query_string and additional_query_string != '*': + narrow_queries.add(additional_query_string) + + narrow_searcher = None + + if narrow_queries is not None: + # Potentially expensive? I don't see another way to do it in + # Whoosh... + narrow_searcher = self.index.searcher() + + for nq in narrow_queries: + recent_narrowed_results = narrow_searcher.search( + self.parser.parse(force_str(nq)), limit=None) + + if len(recent_narrowed_results) <= 0: + return { + 'results': [], + 'hits': 0, + } + + if narrowed_results: + narrowed_results.filter(recent_narrowed_results) + else: + narrowed_results = recent_narrowed_results + + page_num, page_length = self.calculate_page(start_offset, end_offset) + + self.index = self.index.refresh() + raw_results = EmptyResults() + + if self.index.doc_count(): + query = "%s:%s" % (ID, get_identifier(model_instance)) + searcher = self.index.searcher() + parsed_query = self.parser.parse(query) + results = searcher.search(parsed_query) + + if len(results): + raw_results = results[0].more_like_this( + field_name, top=end_offset) + + # Handle the case where the results have been narrowed. + if narrowed_results is not None and hasattr(raw_results, 'filter'): + raw_results.filter(narrowed_results) + + try: + raw_page = ResultsPage(raw_results, page_num, page_length) + except ValueError: + if not self.silently_fail: + raise + + return { + 'results': [], + 'hits': 0, + 'spelling_suggestion': None, + } + + # Because as of Whoosh 2.5.1, it will return the wrong page of + # results if you request something too high. :( + if raw_page.pagenum < page_num: + return { + 'results': [], + 'hits': 0, + 'spelling_suggestion': None, + } + + results = self._process_results(raw_page, result_class=result_class) + searcher.close() + + if hasattr(narrow_searcher, 'close'): + narrow_searcher.close() + + return results + + def _process_results( + self, + raw_page, + highlight=False, + query_string='', + spelling_query=None, + result_class=None): + from haystack import connections + results = [] + + # It's important to grab the hits first before slicing. Otherwise, this + # can cause pagination failures. + hits = len(raw_page) + + if result_class is None: + result_class = SearchResult + + facets = {} + spelling_suggestion = None + unified_index = connections[self.connection_alias].get_unified_index() + indexed_models = unified_index.get_indexed_models() + + for doc_offset, raw_result in enumerate(raw_page): + score = raw_page.score(doc_offset) or 0 + app_label, model_name = raw_result[DJANGO_CT].split('.') + additional_fields = {} + model = haystack_get_model(app_label, model_name) + + if model and model in indexed_models: + for key, value in raw_result.items(): + index = unified_index.get_index(model) + string_key = str(key) + + if string_key in index.fields and hasattr( + index.fields[string_key], 'convert'): + # Special-cased due to the nature of KEYWORD fields. + if index.fields[string_key].is_multivalued: + if value is None or len(value) == 0: + additional_fields[string_key] = [] + else: + additional_fields[string_key] = value.split( + ',') + else: + additional_fields[string_key] = index.fields[string_key].convert( + value) + else: + additional_fields[string_key] = self._to_python(value) + + del (additional_fields[DJANGO_CT]) + del (additional_fields[DJANGO_ID]) + + if highlight: + sa = StemmingAnalyzer() + formatter = WhooshHtmlFormatter('em') + terms = [token.text for token in sa(query_string)] + + whoosh_result = whoosh_highlight( + additional_fields.get(self.content_field_name), + terms, + sa, + ContextFragmenter(), + formatter + ) + additional_fields['highlighted'] = { + self.content_field_name: [whoosh_result], + } + + result = result_class( + app_label, + model_name, + raw_result[DJANGO_ID], + score, + **additional_fields) + results.append(result) + else: + hits -= 1 + + if self.include_spelling: + if spelling_query: + spelling_suggestion = self.create_spelling_suggestion( + spelling_query) + else: + spelling_suggestion = self.create_spelling_suggestion( + query_string) + + return { + 'results': results, + 'hits': hits, + 'facets': facets, + 'spelling_suggestion': spelling_suggestion, + } + + def create_spelling_suggestion(self, query_string): + spelling_suggestion = None + reader = self.index.reader() + corrector = reader.corrector(self.content_field_name) + cleaned_query = force_str(query_string) + + if not query_string: + return spelling_suggestion + + # Clean the string. + for rev_word in self.RESERVED_WORDS: + cleaned_query = cleaned_query.replace(rev_word, '') + + for rev_char in self.RESERVED_CHARACTERS: + cleaned_query = cleaned_query.replace(rev_char, '') + + # Break it down. + query_words = cleaned_query.split() + suggested_words = [] + + for word in query_words: + suggestions = corrector.suggest(word, limit=1) + + if len(suggestions) > 0: + suggested_words.append(suggestions[0]) + + spelling_suggestion = ' '.join(suggested_words) + return spelling_suggestion + + def _from_python(self, value): + """ + Converts Python values to a string for Whoosh. + + Code courtesy of pysolr. + """ + if hasattr(value, 'strftime'): + if not hasattr(value, 'hour'): + value = datetime(value.year, value.month, value.day, 0, 0, 0) + elif isinstance(value, bool): + if value: + value = 'true' + else: + value = 'false' + elif isinstance(value, (list, tuple)): + value = u','.join([force_str(v) for v in value]) + elif isinstance(value, (six.integer_types, float)): + # Leave it alone. + pass + else: + value = force_str(value) + return value + + def _to_python(self, value): + """ + Converts values from Whoosh to native Python values. + + A port of the same method in pysolr, as they deal with data the same way. + """ + if value == 'true': + return True + elif value == 'false': + return False + + if value and isinstance(value, six.string_types): + possible_datetime = DATETIME_REGEX.search(value) + + if possible_datetime: + date_values = possible_datetime.groupdict() + + for dk, dv in date_values.items(): + date_values[dk] = int(dv) + + return datetime( + date_values['year'], + date_values['month'], + date_values['day'], + date_values['hour'], + date_values['minute'], + date_values['second']) + + try: + # Attempt to use json to load the values. + converted_value = json.loads(value) + + # Try to handle most built-in types. + if isinstance( + converted_value, + (list, + tuple, + set, + dict, + six.integer_types, + float, + complex)): + return converted_value + except BaseException: + # If it fails (SyntaxError or its ilk) or we don't trust it, + # continue on. + pass + + return value + + +class WhooshSearchQuery(BaseSearchQuery): + def _convert_datetime(self, date): + if hasattr(date, 'hour'): + return force_str(date.strftime('%Y%m%d%H%M%S')) + else: + return force_str(date.strftime('%Y%m%d000000')) + + def clean(self, query_fragment): + """ + Provides a mechanism for sanitizing user input before presenting the + value to the backend. + + Whoosh 1.X differs here in that you can no longer use a backslash + to escape reserved characters. Instead, the whole word should be + quoted. + """ + words = query_fragment.split() + cleaned_words = [] + + for word in words: + if word in self.backend.RESERVED_WORDS: + word = word.replace(word, word.lower()) + + for char in self.backend.RESERVED_CHARACTERS: + if char in word: + word = "'%s'" % word + break + + cleaned_words.append(word) + + return ' '.join(cleaned_words) + + def build_query_fragment(self, field, filter_type, value): + from haystack import connections + query_frag = '' + is_datetime = False + + if not hasattr(value, 'input_type_name'): + # Handle when we've got a ``ValuesListQuerySet``... + if hasattr(value, 'values_list'): + value = list(value) + + if hasattr(value, 'strftime'): + is_datetime = True + + if isinstance(value, six.string_types) and value != ' ': + # It's not an ``InputType``. Assume ``Clean``. + value = Clean(value) + else: + value = PythonData(value) + + # Prepare the query using the InputType. + prepared_value = value.prepare(self) + + if not isinstance(prepared_value, (set, list, tuple)): + # Then convert whatever we get back to what pysolr wants if needed. + prepared_value = self.backend._from_python(prepared_value) + + # 'content' is a special reserved word, much like 'pk' in + # Django's ORM layer. It indicates 'no special field'. + if field == 'content': + index_fieldname = '' + else: + index_fieldname = u'%s:' % connections[self._using].get_unified_index( + ).get_index_fieldname(field) + + filter_types = { + 'content': '%s', + 'contains': '*%s*', + 'endswith': "*%s", + 'startswith': "%s*", + 'exact': '%s', + 'gt': "{%s to}", + 'gte': "[%s to]", + 'lt': "{to %s}", + 'lte': "[to %s]", + 'fuzzy': u'%s~', + } + + if value.post_process is False: + query_frag = prepared_value + else: + if filter_type in [ + 'content', + 'contains', + 'startswith', + 'endswith', + 'fuzzy']: + if value.input_type_name == 'exact': + query_frag = prepared_value + else: + # Iterate over terms & incorportate the converted form of + # each into the query. + terms = [] + + if isinstance(prepared_value, six.string_types): + possible_values = prepared_value.split(' ') + else: + if is_datetime is True: + prepared_value = self._convert_datetime( + prepared_value) + + possible_values = [prepared_value] + + for possible_value in possible_values: + terms.append( + filter_types[filter_type] % + self.backend._from_python(possible_value)) + + if len(terms) == 1: + query_frag = terms[0] + else: + query_frag = u"(%s)" % " AND ".join(terms) + elif filter_type == 'in': + in_options = [] + + for possible_value in prepared_value: + is_datetime = False + + if hasattr(possible_value, 'strftime'): + is_datetime = True + + pv = self.backend._from_python(possible_value) + + if is_datetime is True: + pv = self._convert_datetime(pv) + + if isinstance(pv, six.string_types) and not is_datetime: + in_options.append('"%s"' % pv) + else: + in_options.append('%s' % pv) + + query_frag = "(%s)" % " OR ".join(in_options) + elif filter_type == 'range': + start = self.backend._from_python(prepared_value[0]) + end = self.backend._from_python(prepared_value[1]) + + if hasattr(prepared_value[0], 'strftime'): + start = self._convert_datetime(start) + + if hasattr(prepared_value[1], 'strftime'): + end = self._convert_datetime(end) + + query_frag = u"[%s to %s]" % (start, end) + elif filter_type == 'exact': + if value.input_type_name == 'exact': + query_frag = prepared_value + else: + prepared_value = Exact(prepared_value).prepare(self) + query_frag = filter_types[filter_type] % prepared_value + else: + if is_datetime is True: + prepared_value = self._convert_datetime(prepared_value) + + query_frag = filter_types[filter_type] % prepared_value + + if len(query_frag) and not isinstance(value, Raw): + if not query_frag.startswith('(') and not query_frag.endswith(')'): + query_frag = "(%s)" % query_frag + + return u"%s%s" % (index_fieldname, query_frag) + + # if not filter_type in ('in', 'range'): + # # 'in' is a bit of a special case, as we don't want to + # # convert a valid list/tuple to string. Defer handling it + # # until later... + # value = self.backend._from_python(value) + + +class WhooshEngine(BaseEngine): + backend = WhooshSearchBackend + query = WhooshSearchQuery diff --git a/src/oauth/__init__.py b/src/DjangoBlog/djangoblog/whoosh_index/MAIN_WRITELOCK similarity index 100% rename from src/oauth/__init__.py rename to src/DjangoBlog/djangoblog/whoosh_index/MAIN_WRITELOCK diff --git a/src/DjangoBlog/djangoblog/whoosh_index/MAIN_pds7j169qpxa6g5f.seg b/src/DjangoBlog/djangoblog/whoosh_index/MAIN_pds7j169qpxa6g5f.seg new file mode 100644 index 0000000..1b65194 Binary files /dev/null and b/src/DjangoBlog/djangoblog/whoosh_index/MAIN_pds7j169qpxa6g5f.seg differ diff --git a/src/DjangoBlog/djangoblog/whoosh_index/MAIN_voc5i24mpy2wego1.seg b/src/DjangoBlog/djangoblog/whoosh_index/MAIN_voc5i24mpy2wego1.seg new file mode 100644 index 0000000..e572117 Binary files /dev/null and b/src/DjangoBlog/djangoblog/whoosh_index/MAIN_voc5i24mpy2wego1.seg differ diff --git a/src/DjangoBlog/djangoblog/whoosh_index/_MAIN_59.toc b/src/DjangoBlog/djangoblog/whoosh_index/_MAIN_59.toc new file mode 100644 index 0000000..fface2e Binary files /dev/null and b/src/DjangoBlog/djangoblog/whoosh_index/_MAIN_59.toc differ diff --git a/src/DjangoBlog/djangoblog/wsgi.py b/src/DjangoBlog/djangoblog/wsgi.py new file mode 100644 index 0000000..2295efd --- /dev/null +++ b/src/DjangoBlog/djangoblog/wsgi.py @@ -0,0 +1,16 @@ +""" +WSGI config for djangoblog project. + +It exposes the WSGI callable as a module-level variable named ``application``. + +For more information on this file, see +https://docs.djangoproject.com/en/1.10/howto/deployment/wsgi/ +""" + +import os + +from django.core.wsgi import get_wsgi_application + +os.environ.setdefault("DJANGO_SETTINGS_MODULE", "djangoblog.settings") + +application = get_wsgi_application() diff --git a/src/docs/README-en.md b/src/DjangoBlog/docs/README-en.md similarity index 100% rename from src/docs/README-en.md rename to src/DjangoBlog/docs/README-en.md diff --git a/src/docs/config-en.md b/src/DjangoBlog/docs/config-en.md similarity index 100% rename from src/docs/config-en.md rename to src/DjangoBlog/docs/config-en.md diff --git a/src/docs/config.md b/src/DjangoBlog/docs/config.md similarity index 100% rename from src/docs/config.md rename to src/DjangoBlog/docs/config.md diff --git a/src/docs/docker-en.md b/src/DjangoBlog/docs/docker-en.md similarity index 100% rename from src/docs/docker-en.md rename to src/DjangoBlog/docs/docker-en.md diff --git a/src/docs/docker.md b/src/DjangoBlog/docs/docker.md similarity index 100% rename from src/docs/docker.md rename to src/DjangoBlog/docs/docker.md diff --git a/src/docs/es.md b/src/DjangoBlog/docs/es.md similarity index 100% rename from src/docs/es.md rename to src/DjangoBlog/docs/es.md diff --git a/src/docs/imgs/alipay.jpg b/src/DjangoBlog/docs/imgs/alipay.jpg similarity index 100% rename from src/docs/imgs/alipay.jpg rename to src/DjangoBlog/docs/imgs/alipay.jpg diff --git a/src/docs/imgs/pycharm_logo.png b/src/DjangoBlog/docs/imgs/pycharm_logo.png similarity index 100% rename from src/docs/imgs/pycharm_logo.png rename to src/DjangoBlog/docs/imgs/pycharm_logo.png diff --git a/src/docs/imgs/wechat.jpg b/src/DjangoBlog/docs/imgs/wechat.jpg similarity index 100% rename from src/docs/imgs/wechat.jpg rename to src/DjangoBlog/docs/imgs/wechat.jpg diff --git a/src/docs/k8s-en.md b/src/DjangoBlog/docs/k8s-en.md similarity index 100% rename from src/docs/k8s-en.md rename to src/DjangoBlog/docs/k8s-en.md diff --git a/src/docs/k8s.md b/src/DjangoBlog/docs/k8s.md similarity index 100% rename from src/docs/k8s.md rename to src/DjangoBlog/docs/k8s.md diff --git a/src/locale/en/LC_MESSAGES/django.mo b/src/DjangoBlog/locale/en/LC_MESSAGES/django.mo similarity index 100% rename from src/locale/en/LC_MESSAGES/django.mo rename to src/DjangoBlog/locale/en/LC_MESSAGES/django.mo diff --git a/src/locale/en/LC_MESSAGES/django.po b/src/DjangoBlog/locale/en/LC_MESSAGES/django.po similarity index 100% rename from src/locale/en/LC_MESSAGES/django.po rename to src/DjangoBlog/locale/en/LC_MESSAGES/django.po diff --git a/src/locale/zh_Hans/LC_MESSAGES/django.mo b/src/DjangoBlog/locale/zh_Hans/LC_MESSAGES/django.mo similarity index 100% rename from src/locale/zh_Hans/LC_MESSAGES/django.mo rename to src/DjangoBlog/locale/zh_Hans/LC_MESSAGES/django.mo diff --git a/src/locale/zh_Hans/LC_MESSAGES/django.po b/src/DjangoBlog/locale/zh_Hans/LC_MESSAGES/django.po similarity index 100% rename from src/locale/zh_Hans/LC_MESSAGES/django.po rename to src/DjangoBlog/locale/zh_Hans/LC_MESSAGES/django.po diff --git a/src/locale/zh_Hant/LC_MESSAGES/django.mo b/src/DjangoBlog/locale/zh_Hant/LC_MESSAGES/django.mo similarity index 100% rename from src/locale/zh_Hant/LC_MESSAGES/django.mo rename to src/DjangoBlog/locale/zh_Hant/LC_MESSAGES/django.mo diff --git a/src/locale/zh_Hant/LC_MESSAGES/django.po b/src/DjangoBlog/locale/zh_Hant/LC_MESSAGES/django.po similarity index 100% rename from src/locale/zh_Hant/LC_MESSAGES/django.po rename to src/DjangoBlog/locale/zh_Hant/LC_MESSAGES/django.po diff --git a/src/DjangoBlog/logs/djangoblog.log b/src/DjangoBlog/logs/djangoblog.log new file mode 100644 index 0000000..efe0d9b --- /dev/null +++ b/src/DjangoBlog/logs/djangoblog.log @@ -0,0 +1,221 @@ +[2025-10-12 19:28:40,696] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] 文章结尾版权声明 initialized. +[2025-10-12 19:28:40,696] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] 文章结尾版权声明 initialized. +[2025-10-12 19:28:40,696] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: article_copyright +[2025-10-12 19:28:40,696] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: article_copyright +[2025-10-12 19:28:40,699] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] 阅读时间预测 initialized. +[2025-10-12 19:28:40,699] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] 阅读时间预测 initialized. +[2025-10-12 19:28:40,699] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: reading_time +[2025-10-12 19:28:40,699] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: reading_time +[2025-10-12 19:28:40,702] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] 外部链接处理器 initialized. +[2025-10-12 19:28:40,702] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] 外部链接处理器 initialized. +[2025-10-12 19:28:40,702] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: external_links +[2025-10-12 19:28:40,702] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: external_links +[2025-10-12 19:28:40,704] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] 文章浏览次数统计 initialized. +[2025-10-12 19:28:40,704] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] 文章浏览次数统计 initialized. +[2025-10-12 19:28:40,704] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: view_count +[2025-10-12 19:28:40,704] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: view_count +[2025-10-12 19:28:40,708] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] SEO 优化器 initialized. +[2025-10-12 19:28:40,708] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] SEO 优化器 initialized. +[2025-10-12 19:28:40,708] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: seo_optimizer +[2025-10-12 19:28:40,708] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: seo_optimizer +[2025-10-12 19:28:41,604] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] 文章结尾版权声明 initialized. +[2025-10-12 19:28:41,604] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] 文章结尾版权声明 initialized. +[2025-10-12 19:28:41,605] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: article_copyright +[2025-10-12 19:28:41,605] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: article_copyright +[2025-10-12 19:28:41,606] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] 阅读时间预测 initialized. +[2025-10-12 19:28:41,606] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] 阅读时间预测 initialized. +[2025-10-12 19:28:41,606] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: reading_time +[2025-10-12 19:28:41,606] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: reading_time +[2025-10-12 19:28:41,608] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] 外部链接处理器 initialized. +[2025-10-12 19:28:41,608] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] 外部链接处理器 initialized. +[2025-10-12 19:28:41,608] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: external_links +[2025-10-12 19:28:41,608] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: external_links +[2025-10-12 19:28:41,609] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] 文章浏览次数统计 initialized. +[2025-10-12 19:28:41,609] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] 文章浏览次数统计 initialized. +[2025-10-12 19:28:41,610] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: view_count +[2025-10-12 19:28:41,610] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: view_count +[2025-10-12 19:28:41,611] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] SEO 优化器 initialized. +[2025-10-12 19:28:41,611] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] SEO 优化器 initialized. +[2025-10-12 19:28:41,612] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: seo_optimizer +[2025-10-12 19:28:41,612] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: seo_optimizer +[2025-10-12 19:28:41,639] INFO [django.utils.autoreload.run_with_reloader:667 autoreload] Watching for file changes with StatReloader +[2025-10-12 19:28:46,951] INFO [blog.views.get_queryset_from_cache:75 views] set view cache.key:index_1 +[2025-10-12 19:28:46,966] INFO [blog.context_processors.seo_processor:17 context_processors] set processor cache. +[2025-10-12 19:28:46,983] INFO [djangoblog.utils.get_blog_setting:171 utils] set cache get_blog_setting +[2025-10-12 19:28:46,983] INFO [djangoblog.utils.get_blog_setting:171 utils] set cache get_blog_setting +[2025-10-12 19:28:47,720] INFO [blog.templatetags.blog_tags.load_sidebar:162 blog_tags] load sidebar +[2025-10-12 19:28:47,783] INFO [blog.templatetags.blog_tags.load_sidebar:214 blog_tags] set sidebar cache.key:sidebari +[2025-10-12 19:30:46,157] INFO [django.utils.autoreload.trigger_reload:265 autoreload] D:\software_project\src\DjangoBlog\servermanager\api\blogapi.py changed, reloading. +[2025-10-12 19:30:47,195] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] 文章结尾版权声明 initialized. +[2025-10-12 19:30:47,195] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] 文章结尾版权声明 initialized. +[2025-10-12 19:30:47,196] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: article_copyright +[2025-10-12 19:30:47,196] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: article_copyright +[2025-10-12 19:30:47,197] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] 阅读时间预测 initialized. +[2025-10-12 19:30:47,197] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] 阅读时间预测 initialized. +[2025-10-12 19:30:47,198] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: reading_time +[2025-10-12 19:30:47,198] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: reading_time +[2025-10-12 19:30:47,199] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] 外部链接处理器 initialized. +[2025-10-12 19:30:47,199] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] 外部链接处理器 initialized. +[2025-10-12 19:30:47,200] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: external_links +[2025-10-12 19:30:47,200] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: external_links +[2025-10-12 19:30:47,201] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] 文章浏览次数统计 initialized. +[2025-10-12 19:30:47,201] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] 文章浏览次数统计 initialized. +[2025-10-12 19:30:47,201] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: view_count +[2025-10-12 19:30:47,201] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: view_count +[2025-10-12 19:30:47,203] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] SEO 优化器 initialized. +[2025-10-12 19:30:47,203] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] SEO 优化器 initialized. +[2025-10-12 19:30:47,203] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: seo_optimizer +[2025-10-12 19:30:47,203] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: seo_optimizer +[2025-10-12 19:30:47,234] INFO [django.utils.autoreload.run_with_reloader:667 autoreload] Watching for file changes with StatReloader +[2025-10-12 19:41:06,263] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] 文章结尾版权声明 initialized. +[2025-10-12 19:41:06,263] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] 文章结尾版权声明 initialized. +[2025-10-12 19:41:06,264] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: article_copyright +[2025-10-12 19:41:06,264] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: article_copyright +[2025-10-12 19:41:06,265] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] 阅读时间预测 initialized. +[2025-10-12 19:41:06,265] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] 阅读时间预测 initialized. +[2025-10-12 19:41:06,265] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: reading_time +[2025-10-12 19:41:06,265] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: reading_time +[2025-10-12 19:41:06,266] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] 外部链接处理器 initialized. +[2025-10-12 19:41:06,266] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] 外部链接处理器 initialized. +[2025-10-12 19:41:06,266] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: external_links +[2025-10-12 19:41:06,266] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: external_links +[2025-10-12 19:41:06,267] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] 文章浏览次数统计 initialized. +[2025-10-12 19:41:06,267] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] 文章浏览次数统计 initialized. +[2025-10-12 19:41:06,267] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: view_count +[2025-10-12 19:41:06,267] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: view_count +[2025-10-12 19:41:06,268] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] SEO 优化器 initialized. +[2025-10-12 19:41:06,268] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] SEO 优化器 initialized. +[2025-10-12 19:41:06,268] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: seo_optimizer +[2025-10-12 19:41:06,268] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: seo_optimizer +[2025-10-12 19:41:15,956] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] 文章结尾版权声明 initialized. +[2025-10-12 19:41:15,956] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] 文章结尾版权声明 initialized. +[2025-10-12 19:41:15,956] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: article_copyright +[2025-10-12 19:41:15,956] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: article_copyright +[2025-10-12 19:41:15,957] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] 阅读时间预测 initialized. +[2025-10-12 19:41:15,957] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] 阅读时间预测 initialized. +[2025-10-12 19:41:15,957] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: reading_time +[2025-10-12 19:41:15,957] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: reading_time +[2025-10-12 19:41:15,958] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] 外部链接处理器 initialized. +[2025-10-12 19:41:15,958] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] 外部链接处理器 initialized. +[2025-10-12 19:41:15,958] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: external_links +[2025-10-12 19:41:15,958] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: external_links +[2025-10-12 19:41:15,959] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] 文章浏览次数统计 initialized. +[2025-10-12 19:41:15,959] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] 文章浏览次数统计 initialized. +[2025-10-12 19:41:15,959] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: view_count +[2025-10-12 19:41:15,959] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: view_count +[2025-10-12 19:41:15,960] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] SEO 优化器 initialized. +[2025-10-12 19:41:15,960] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] SEO 优化器 initialized. +[2025-10-12 19:41:15,960] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: seo_optimizer +[2025-10-12 19:41:15,960] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: seo_optimizer +[2025-10-12 19:41:29,188] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] 文章结尾版权声明 initialized. +[2025-10-12 19:41:29,188] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] 文章结尾版权声明 initialized. +[2025-10-12 19:41:29,188] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: article_copyright +[2025-10-12 19:41:29,188] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: article_copyright +[2025-10-12 19:41:29,190] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] 阅读时间预测 initialized. +[2025-10-12 19:41:29,190] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] 阅读时间预测 initialized. +[2025-10-12 19:41:29,190] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: reading_time +[2025-10-12 19:41:29,190] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: reading_time +[2025-10-12 19:41:29,191] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] 外部链接处理器 initialized. +[2025-10-12 19:41:29,191] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] 外部链接处理器 initialized. +[2025-10-12 19:41:29,191] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: external_links +[2025-10-12 19:41:29,191] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: external_links +[2025-10-12 19:41:29,192] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] 文章浏览次数统计 initialized. +[2025-10-12 19:41:29,192] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] 文章浏览次数统计 initialized. +[2025-10-12 19:41:29,192] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: view_count +[2025-10-12 19:41:29,192] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: view_count +[2025-10-12 19:41:29,193] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] SEO 优化器 initialized. +[2025-10-12 19:41:29,193] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] SEO 优化器 initialized. +[2025-10-12 19:41:29,193] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: seo_optimizer +[2025-10-12 19:41:29,193] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: seo_optimizer +[2025-10-12 19:41:30,115] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] 文章结尾版权声明 initialized. +[2025-10-12 19:41:30,115] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] 文章结尾版权声明 initialized. +[2025-10-12 19:41:30,115] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: article_copyright +[2025-10-12 19:41:30,115] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: article_copyright +[2025-10-12 19:41:30,116] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] 阅读时间预测 initialized. +[2025-10-12 19:41:30,116] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] 阅读时间预测 initialized. +[2025-10-12 19:41:30,116] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: reading_time +[2025-10-12 19:41:30,116] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: reading_time +[2025-10-12 19:41:30,117] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] 外部链接处理器 initialized. +[2025-10-12 19:41:30,117] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] 外部链接处理器 initialized. +[2025-10-12 19:41:30,117] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: external_links +[2025-10-12 19:41:30,117] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: external_links +[2025-10-12 19:41:30,118] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] 文章浏览次数统计 initialized. +[2025-10-12 19:41:30,118] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] 文章浏览次数统计 initialized. +[2025-10-12 19:41:30,119] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: view_count +[2025-10-12 19:41:30,119] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: view_count +[2025-10-12 19:41:30,120] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] SEO 优化器 initialized. +[2025-10-12 19:41:30,120] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] SEO 优化器 initialized. +[2025-10-12 19:41:30,120] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: seo_optimizer +[2025-10-12 19:41:30,120] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: seo_optimizer +[2025-10-12 19:41:30,144] INFO [django.utils.autoreload.run_with_reloader:667 autoreload] Watching for file changes with StatReloader +[2025-10-12 19:41:33,755] INFO [blog.views.get_queryset_from_cache:75 views] set view cache.key:index_1 +[2025-10-12 19:38:05,975] INFO [django.utils.autoreload.trigger_reload:265 autoreload] D:\software_project\src\DjangoBlog\djangoblog\settings.py changed, reloading. +[2025-10-12 19:41:51,725] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] 文章结尾版权声明 initialized. +[2025-10-12 19:41:51,725] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] 文章结尾版权声明 initialized. +[2025-10-12 19:41:51,725] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: article_copyright +[2025-10-12 19:41:51,725] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: article_copyright +[2025-10-12 19:41:51,726] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] 阅读时间预测 initialized. +[2025-10-12 19:41:51,726] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] 阅读时间预测 initialized. +[2025-10-12 19:41:51,727] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: reading_time +[2025-10-12 19:41:51,727] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: reading_time +[2025-10-12 19:41:51,728] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] 外部链接处理器 initialized. +[2025-10-12 19:41:51,728] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] 外部链接处理器 initialized. +[2025-10-12 19:41:51,728] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: external_links +[2025-10-12 19:41:51,728] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: external_links +[2025-10-12 19:41:51,729] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] 文章浏览次数统计 initialized. +[2025-10-12 19:41:51,729] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] 文章浏览次数统计 initialized. +[2025-10-12 19:41:51,729] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: view_count +[2025-10-12 19:41:51,729] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: view_count +[2025-10-12 19:41:51,731] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] SEO 优化器 initialized. +[2025-10-12 19:41:51,731] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] SEO 优化器 initialized. +[2025-10-12 19:41:51,732] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: seo_optimizer +[2025-10-12 19:41:51,732] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: seo_optimizer +[2025-10-12 19:41:51,756] INFO [django.utils.autoreload.run_with_reloader:667 autoreload] Watching for file changes with StatReloader +[2025-10-12 19:42:05,845] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] 文章结尾版权声明 initialized. +[2025-10-12 19:42:05,845] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] 文章结尾版权声明 initialized. +[2025-10-12 19:42:05,846] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: article_copyright +[2025-10-12 19:42:05,846] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: article_copyright +[2025-10-12 19:42:05,847] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] 阅读时间预测 initialized. +[2025-10-12 19:42:05,847] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] 阅读时间预测 initialized. +[2025-10-12 19:42:05,847] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: reading_time +[2025-10-12 19:42:05,847] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: reading_time +[2025-10-12 19:42:05,848] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] 外部链接处理器 initialized. +[2025-10-12 19:42:05,848] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] 外部链接处理器 initialized. +[2025-10-12 19:42:05,849] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: external_links +[2025-10-12 19:42:05,849] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: external_links +[2025-10-12 19:42:05,850] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] 文章浏览次数统计 initialized. +[2025-10-12 19:42:05,850] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] 文章浏览次数统计 initialized. +[2025-10-12 19:42:05,850] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: view_count +[2025-10-12 19:42:05,850] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: view_count +[2025-10-12 19:42:05,851] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] SEO 优化器 initialized. +[2025-10-12 19:42:05,851] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] SEO 优化器 initialized. +[2025-10-12 19:42:05,851] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: seo_optimizer +[2025-10-12 19:42:05,851] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: seo_optimizer +[2025-10-12 19:42:06,754] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] 文章结尾版权声明 initialized. +[2025-10-12 19:42:06,754] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] 文章结尾版权声明 initialized. +[2025-10-12 19:42:06,754] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: article_copyright +[2025-10-12 19:42:06,754] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: article_copyright +[2025-10-12 19:42:06,755] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] 阅读时间预测 initialized. +[2025-10-12 19:42:06,755] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] 阅读时间预测 initialized. +[2025-10-12 19:42:06,755] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: reading_time +[2025-10-12 19:42:06,755] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: reading_time +[2025-10-12 19:42:06,757] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] 外部链接处理器 initialized. +[2025-10-12 19:42:06,757] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] 外部链接处理器 initialized. +[2025-10-12 19:42:06,758] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: external_links +[2025-10-12 19:42:06,758] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: external_links +[2025-10-12 19:42:06,759] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] 文章浏览次数统计 initialized. +[2025-10-12 19:42:06,759] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] 文章浏览次数统计 initialized. +[2025-10-12 19:42:06,772] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: view_count +[2025-10-12 19:42:06,772] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: view_count +[2025-10-12 19:42:06,775] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] SEO 优化器 initialized. +[2025-10-12 19:42:06,775] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] SEO 优化器 initialized. +[2025-10-12 19:42:06,775] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: seo_optimizer +[2025-10-12 19:42:06,775] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: seo_optimizer +[2025-10-12 19:42:06,808] INFO [django.utils.autoreload.run_with_reloader:667 autoreload] Watching for file changes with StatReloader +[2025-10-12 19:42:37,150] INFO [blog.views.get_queryset_from_cache:75 views] set view cache.key:index_1 +[2025-10-12 19:42:37,158] INFO [blog.context_processors.seo_processor:17 context_processors] set processor cache. +[2025-10-12 19:42:37,164] INFO [djangoblog.utils.get_blog_setting:171 utils] set cache get_blog_setting +[2025-10-12 19:42:37,164] INFO [djangoblog.utils.get_blog_setting:171 utils] set cache get_blog_setting +[2025-10-12 19:42:37,461] INFO [blog.templatetags.blog_tags.load_sidebar:162 blog_tags] load sidebar +[2025-10-12 19:42:37,480] INFO [blog.templatetags.blog_tags.load_sidebar:214 blog_tags] set sidebar cache.key:sidebari +[2025-10-12 19:42:39,464] INFO [blog.views.get_queryset_from_cache:70 views] get view cache.key:index_1 diff --git a/src/DjangoBlog/logs/djangoblog.log.2025-09-16 b/src/DjangoBlog/logs/djangoblog.log.2025-09-16 new file mode 100644 index 0000000..df4e2bd --- /dev/null +++ b/src/DjangoBlog/logs/djangoblog.log.2025-09-16 @@ -0,0 +1,353 @@ +[2025-09-16 19:40:41,270] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] 文章结尾版权声明 initialized. +[2025-09-16 19:40:41,270] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] 文章结尾版权声明 initialized. +[2025-09-16 19:40:41,272] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: article_copyright +[2025-09-16 19:40:41,272] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: article_copyright +[2025-09-16 19:40:41,279] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] 阅读时间预测 initialized. +[2025-09-16 19:40:41,279] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] 阅读时间预测 initialized. +[2025-09-16 19:40:41,280] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: reading_time +[2025-09-16 19:40:41,280] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: reading_time +[2025-09-16 19:40:41,286] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] 外部链接处理器 initialized. +[2025-09-16 19:40:41,286] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] 外部链接处理器 initialized. +[2025-09-16 19:40:41,286] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: external_links +[2025-09-16 19:40:41,286] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: external_links +[2025-09-16 19:40:41,293] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] 文章浏览次数统计 initialized. +[2025-09-16 19:40:41,293] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] 文章浏览次数统计 initialized. +[2025-09-16 19:40:41,293] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: view_count +[2025-09-16 19:40:41,293] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: view_count +[2025-09-16 19:40:41,307] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] SEO 优化器 initialized. +[2025-09-16 19:40:41,307] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] SEO 优化器 initialized. +[2025-09-16 19:40:41,310] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: seo_optimizer +[2025-09-16 19:40:41,310] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: seo_optimizer +[2025-09-16 19:40:46,217] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] 文章结尾版权声明 initialized. +[2025-09-16 19:40:46,217] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] 文章结尾版权声明 initialized. +[2025-09-16 19:40:46,218] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: article_copyright +[2025-09-16 19:40:46,218] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: article_copyright +[2025-09-16 19:40:46,220] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] 阅读时间预测 initialized. +[2025-09-16 19:40:46,220] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] 阅读时间预测 initialized. +[2025-09-16 19:40:46,221] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: reading_time +[2025-09-16 19:40:46,221] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: reading_time +[2025-09-16 19:40:46,223] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] 外部链接处理器 initialized. +[2025-09-16 19:40:46,223] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] 外部链接处理器 initialized. +[2025-09-16 19:40:46,225] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: external_links +[2025-09-16 19:40:46,225] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: external_links +[2025-09-16 19:40:46,227] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] 文章浏览次数统计 initialized. +[2025-09-16 19:40:46,227] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] 文章浏览次数统计 initialized. +[2025-09-16 19:40:46,227] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: view_count +[2025-09-16 19:40:46,227] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: view_count +[2025-09-16 19:40:46,230] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] SEO 优化器 initialized. +[2025-09-16 19:40:46,230] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] SEO 优化器 initialized. +[2025-09-16 19:40:46,232] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: seo_optimizer +[2025-09-16 19:40:46,232] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: seo_optimizer +[2025-09-16 19:40:48,035] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] 文章结尾版权声明 initialized. +[2025-09-16 19:40:48,035] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] 文章结尾版权声明 initialized. +[2025-09-16 19:40:48,036] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: article_copyright +[2025-09-16 19:40:48,036] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: article_copyright +[2025-09-16 19:40:48,038] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] 阅读时间预测 initialized. +[2025-09-16 19:40:48,038] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] 阅读时间预测 initialized. +[2025-09-16 19:40:48,039] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: reading_time +[2025-09-16 19:40:48,039] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: reading_time +[2025-09-16 19:40:48,042] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] 外部链接处理器 initialized. +[2025-09-16 19:40:48,042] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] 外部链接处理器 initialized. +[2025-09-16 19:40:48,043] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: external_links +[2025-09-16 19:40:48,043] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: external_links +[2025-09-16 19:40:48,043] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] 文章浏览次数统计 initialized. +[2025-09-16 19:40:48,043] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] 文章浏览次数统计 initialized. +[2025-09-16 19:40:48,045] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: view_count +[2025-09-16 19:40:48,045] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: view_count +[2025-09-16 19:40:48,046] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] SEO 优化器 initialized. +[2025-09-16 19:40:48,046] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] SEO 优化器 initialized. +[2025-09-16 19:40:48,046] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: seo_optimizer +[2025-09-16 19:40:48,046] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: seo_optimizer +[2025-09-16 19:42:35,121] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] 文章结尾版权声明 initialized. +[2025-09-16 19:42:35,121] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] 文章结尾版权声明 initialized. +[2025-09-16 19:42:35,123] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: article_copyright +[2025-09-16 19:42:35,123] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: article_copyright +[2025-09-16 19:42:35,129] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] 阅读时间预测 initialized. +[2025-09-16 19:42:35,129] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] 阅读时间预测 initialized. +[2025-09-16 19:42:35,130] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: reading_time +[2025-09-16 19:42:35,130] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: reading_time +[2025-09-16 19:42:35,132] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] 外部链接处理器 initialized. +[2025-09-16 19:42:35,132] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] 外部链接处理器 initialized. +[2025-09-16 19:42:35,133] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: external_links +[2025-09-16 19:42:35,133] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: external_links +[2025-09-16 19:42:35,134] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] 文章浏览次数统计 initialized. +[2025-09-16 19:42:35,134] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] 文章浏览次数统计 initialized. +[2025-09-16 19:42:35,134] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: view_count +[2025-09-16 19:42:35,134] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: view_count +[2025-09-16 19:42:35,136] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] SEO 优化器 initialized. +[2025-09-16 19:42:35,136] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] SEO 优化器 initialized. +[2025-09-16 19:42:35,137] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: seo_optimizer +[2025-09-16 19:42:35,137] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: seo_optimizer +[2025-09-16 19:42:44,257] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] 文章结尾版权声明 initialized. +[2025-09-16 19:42:44,257] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] 文章结尾版权声明 initialized. +[2025-09-16 19:42:44,258] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: article_copyright +[2025-09-16 19:42:44,258] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: article_copyright +[2025-09-16 19:42:44,260] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] 阅读时间预测 initialized. +[2025-09-16 19:42:44,260] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] 阅读时间预测 initialized. +[2025-09-16 19:42:44,261] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: reading_time +[2025-09-16 19:42:44,261] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: reading_time +[2025-09-16 19:42:44,263] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] 外部链接处理器 initialized. +[2025-09-16 19:42:44,263] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] 外部链接处理器 initialized. +[2025-09-16 19:42:44,263] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: external_links +[2025-09-16 19:42:44,263] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: external_links +[2025-09-16 19:42:44,265] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] 文章浏览次数统计 initialized. +[2025-09-16 19:42:44,265] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] 文章浏览次数统计 initialized. +[2025-09-16 19:42:44,265] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: view_count +[2025-09-16 19:42:44,265] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: view_count +[2025-09-16 19:42:44,267] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] SEO 优化器 initialized. +[2025-09-16 19:42:44,267] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] SEO 优化器 initialized. +[2025-09-16 19:42:44,267] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: seo_optimizer +[2025-09-16 19:42:44,267] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: seo_optimizer +[2025-09-16 19:42:57,770] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] 文章结尾版权声明 initialized. +[2025-09-16 19:42:57,770] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] 文章结尾版权声明 initialized. +[2025-09-16 19:42:57,770] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: article_copyright +[2025-09-16 19:42:57,770] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: article_copyright +[2025-09-16 19:42:57,775] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] 阅读时间预测 initialized. +[2025-09-16 19:42:57,775] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] 阅读时间预测 initialized. +[2025-09-16 19:42:57,775] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: reading_time +[2025-09-16 19:42:57,775] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: reading_time +[2025-09-16 19:42:57,778] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] 外部链接处理器 initialized. +[2025-09-16 19:42:57,778] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] 外部链接处理器 initialized. +[2025-09-16 19:42:57,778] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: external_links +[2025-09-16 19:42:57,778] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: external_links +[2025-09-16 19:42:57,780] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] 文章浏览次数统计 initialized. +[2025-09-16 19:42:57,780] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] 文章浏览次数统计 initialized. +[2025-09-16 19:42:57,780] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: view_count +[2025-09-16 19:42:57,780] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: view_count +[2025-09-16 19:42:57,781] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] SEO 优化器 initialized. +[2025-09-16 19:42:57,781] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] SEO 优化器 initialized. +[2025-09-16 19:42:57,782] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: seo_optimizer +[2025-09-16 19:42:57,782] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: seo_optimizer +[2025-09-16 19:43:30,820] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] 文章结尾版权声明 initialized. +[2025-09-16 19:43:30,820] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] 文章结尾版权声明 initialized. +[2025-09-16 19:43:30,821] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: article_copyright +[2025-09-16 19:43:30,821] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: article_copyright +[2025-09-16 19:43:30,824] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] 阅读时间预测 initialized. +[2025-09-16 19:43:30,824] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] 阅读时间预测 initialized. +[2025-09-16 19:43:30,824] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: reading_time +[2025-09-16 19:43:30,824] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: reading_time +[2025-09-16 19:43:30,825] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] 外部链接处理器 initialized. +[2025-09-16 19:43:30,825] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] 外部链接处理器 initialized. +[2025-09-16 19:43:30,826] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: external_links +[2025-09-16 19:43:30,826] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: external_links +[2025-09-16 19:43:30,827] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] 文章浏览次数统计 initialized. +[2025-09-16 19:43:30,827] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] 文章浏览次数统计 initialized. +[2025-09-16 19:43:30,827] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: view_count +[2025-09-16 19:43:30,827] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: view_count +[2025-09-16 19:43:30,829] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] SEO 优化器 initialized. +[2025-09-16 19:43:30,829] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] SEO 优化器 initialized. +[2025-09-16 19:43:30,829] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: seo_optimizer +[2025-09-16 19:43:30,829] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: seo_optimizer +[2025-09-16 19:43:34,023] INFO [djangoblog.spider_notify.baidu_notify:15 spider_notify] {"remain":5,"success":0,"not_same_site":["https://example.com/author/%E6%B5%8B%E8%AF%95%E7%94%A8%E6%88%B7.html"]} +[2025-09-16 19:43:34,023] INFO [djangoblog.spider_notify.baidu_notify:15 spider_notify] {"remain":5,"success":0,"not_same_site":["https://example.com/author/%E6%B5%8B%E8%AF%95%E7%94%A8%E6%88%B7.html"]} +[2025-09-16 19:43:34,385] INFO [djangoblog.spider_notify.baidu_notify:15 spider_notify] {"remain":5,"success":0,"not_same_site":["https://example.com/category/wo-shi-fu-lei-mu.html"]} +[2025-09-16 19:43:34,385] INFO [djangoblog.spider_notify.baidu_notify:15 spider_notify] {"remain":5,"success":0,"not_same_site":["https://example.com/category/wo-shi-fu-lei-mu.html"]} +[2025-09-16 19:43:34,691] INFO [djangoblog.spider_notify.baidu_notify:15 spider_notify] {"remain":5,"success":0,"not_same_site":["https://example.com/category/zi-lei-mu.html"]} +[2025-09-16 19:43:34,691] INFO [djangoblog.spider_notify.baidu_notify:15 spider_notify] {"remain":5,"success":0,"not_same_site":["https://example.com/category/zi-lei-mu.html"]} +[2025-09-16 19:43:34,956] INFO [djangoblog.spider_notify.baidu_notify:15 spider_notify] {"remain":5,"success":0,"not_same_site":["https://example.com/category/zi-lei-mu.html"]} +[2025-09-16 19:43:34,956] INFO [djangoblog.spider_notify.baidu_notify:15 spider_notify] {"remain":5,"success":0,"not_same_site":["https://example.com/category/zi-lei-mu.html"]} +[2025-09-16 19:43:35,089] INFO [djangoblog.spider_notify.baidu_notify:15 spider_notify] {"remain":5,"success":0,"not_same_site":["https://example.com/tag/biao-qian.html"]} +[2025-09-16 19:43:35,089] INFO [djangoblog.spider_notify.baidu_notify:15 spider_notify] {"remain":5,"success":0,"not_same_site":["https://example.com/tag/biao-qian.html"]} +[2025-09-16 19:43:36,433] INFO [djangoblog.spider_notify.baidu_notify:15 spider_notify] {"remain":5,"success":0,"not_same_site":["https://example.com/article/2025/9/16/1.html"]} +[2025-09-16 19:43:36,433] INFO [djangoblog.spider_notify.baidu_notify:15 spider_notify] {"remain":5,"success":0,"not_same_site":["https://example.com/article/2025/9/16/1.html"]} +[2025-09-16 19:43:36,740] INFO [djangoblog.spider_notify.baidu_notify:15 spider_notify] {"remain":5,"success":0,"not_same_site":["https://example.com/tag/biao-qian-1.html"]} +[2025-09-16 19:43:36,740] INFO [djangoblog.spider_notify.baidu_notify:15 spider_notify] {"remain":5,"success":0,"not_same_site":["https://example.com/tag/biao-qian-1.html"]} +[2025-09-16 19:43:36,943] INFO [djangoblog.spider_notify.baidu_notify:15 spider_notify] {"remain":5,"success":0,"not_same_site":["https://example.com/article/2025/9/16/1.html"]} +[2025-09-16 19:43:36,943] INFO [djangoblog.spider_notify.baidu_notify:15 spider_notify] {"remain":5,"success":0,"not_same_site":["https://example.com/article/2025/9/16/1.html"]} +[2025-09-16 19:43:37,182] INFO [djangoblog.spider_notify.baidu_notify:15 spider_notify] {"remain":5,"success":0,"not_same_site":["https://example.com/article/2025/9/16/2.html"]} +[2025-09-16 19:43:37,182] INFO [djangoblog.spider_notify.baidu_notify:15 spider_notify] {"remain":5,"success":0,"not_same_site":["https://example.com/article/2025/9/16/2.html"]} +[2025-09-16 19:43:37,457] INFO [djangoblog.spider_notify.baidu_notify:15 spider_notify] {"remain":5,"success":0,"not_same_site":["https://example.com/tag/biao-qian-2.html"]} +[2025-09-16 19:43:37,457] INFO [djangoblog.spider_notify.baidu_notify:15 spider_notify] {"remain":5,"success":0,"not_same_site":["https://example.com/tag/biao-qian-2.html"]} +[2025-09-16 19:43:37,765] INFO [djangoblog.spider_notify.baidu_notify:15 spider_notify] {"remain":5,"success":0,"not_same_site":["https://example.com/article/2025/9/16/2.html"]} +[2025-09-16 19:43:37,765] INFO [djangoblog.spider_notify.baidu_notify:15 spider_notify] {"remain":5,"success":0,"not_same_site":["https://example.com/article/2025/9/16/2.html"]} +[2025-09-16 19:43:37,866] INFO [djangoblog.spider_notify.baidu_notify:15 spider_notify] {"remain":5,"success":0,"not_same_site":["https://example.com/article/2025/9/16/3.html"]} +[2025-09-16 19:43:37,866] INFO [djangoblog.spider_notify.baidu_notify:15 spider_notify] {"remain":5,"success":0,"not_same_site":["https://example.com/article/2025/9/16/3.html"]} +[2025-09-16 19:43:38,001] INFO [djangoblog.spider_notify.baidu_notify:15 spider_notify] {"remain":5,"success":0,"not_same_site":["https://example.com/tag/biao-qian-3.html"]} +[2025-09-16 19:43:38,001] INFO [djangoblog.spider_notify.baidu_notify:15 spider_notify] {"remain":5,"success":0,"not_same_site":["https://example.com/tag/biao-qian-3.html"]} +[2025-09-16 19:43:38,197] INFO [djangoblog.spider_notify.baidu_notify:15 spider_notify] {"remain":5,"success":0,"not_same_site":["https://example.com/article/2025/9/16/3.html"]} +[2025-09-16 19:43:38,197] INFO [djangoblog.spider_notify.baidu_notify:15 spider_notify] {"remain":5,"success":0,"not_same_site":["https://example.com/article/2025/9/16/3.html"]} +[2025-09-16 19:43:38,582] INFO [djangoblog.spider_notify.baidu_notify:15 spider_notify] {"remain":5,"success":0,"not_same_site":["https://example.com/article/2025/9/16/4.html"]} +[2025-09-16 19:43:38,582] INFO [djangoblog.spider_notify.baidu_notify:15 spider_notify] {"remain":5,"success":0,"not_same_site":["https://example.com/article/2025/9/16/4.html"]} +[2025-09-16 19:43:38,891] INFO [djangoblog.spider_notify.baidu_notify:15 spider_notify] {"remain":5,"success":0,"not_same_site":["https://example.com/tag/biao-qian-4.html"]} +[2025-09-16 19:43:38,891] INFO [djangoblog.spider_notify.baidu_notify:15 spider_notify] {"remain":5,"success":0,"not_same_site":["https://example.com/tag/biao-qian-4.html"]} +[2025-09-16 19:43:39,101] INFO [djangoblog.spider_notify.baidu_notify:15 spider_notify] {"remain":5,"success":0,"not_same_site":["https://example.com/article/2025/9/16/4.html"]} +[2025-09-16 19:43:39,101] INFO [djangoblog.spider_notify.baidu_notify:15 spider_notify] {"remain":5,"success":0,"not_same_site":["https://example.com/article/2025/9/16/4.html"]} +[2025-09-16 19:43:39,199] INFO [djangoblog.spider_notify.baidu_notify:15 spider_notify] {"remain":5,"success":0,"not_same_site":["https://example.com/article/2025/9/16/5.html"]} +[2025-09-16 19:43:39,199] INFO [djangoblog.spider_notify.baidu_notify:15 spider_notify] {"remain":5,"success":0,"not_same_site":["https://example.com/article/2025/9/16/5.html"]} +[2025-09-16 19:43:39,402] INFO [djangoblog.spider_notify.baidu_notify:15 spider_notify] {"remain":5,"success":0,"not_same_site":["https://example.com/tag/biao-qian-5.html"]} +[2025-09-16 19:43:39,402] INFO [djangoblog.spider_notify.baidu_notify:15 spider_notify] {"remain":5,"success":0,"not_same_site":["https://example.com/tag/biao-qian-5.html"]} +[2025-09-16 19:43:39,586] INFO [djangoblog.spider_notify.baidu_notify:15 spider_notify] {"remain":5,"success":0,"not_same_site":["https://example.com/article/2025/9/16/5.html"]} +[2025-09-16 19:43:39,586] INFO [djangoblog.spider_notify.baidu_notify:15 spider_notify] {"remain":5,"success":0,"not_same_site":["https://example.com/article/2025/9/16/5.html"]} +[2025-09-16 19:43:39,711] INFO [djangoblog.spider_notify.baidu_notify:15 spider_notify] {"remain":5,"success":0,"not_same_site":["https://example.com/article/2025/9/16/6.html"]} +[2025-09-16 19:43:39,711] INFO [djangoblog.spider_notify.baidu_notify:15 spider_notify] {"remain":5,"success":0,"not_same_site":["https://example.com/article/2025/9/16/6.html"]} +[2025-09-16 19:43:39,961] INFO [djangoblog.spider_notify.baidu_notify:15 spider_notify] {"remain":5,"success":0,"not_same_site":["https://example.com/tag/biao-qian-6.html"]} +[2025-09-16 19:43:39,961] INFO [djangoblog.spider_notify.baidu_notify:15 spider_notify] {"remain":5,"success":0,"not_same_site":["https://example.com/tag/biao-qian-6.html"]} +[2025-09-16 19:43:40,253] INFO [djangoblog.spider_notify.baidu_notify:15 spider_notify] {"remain":5,"success":0,"not_same_site":["https://example.com/article/2025/9/16/6.html"]} +[2025-09-16 19:43:40,253] INFO [djangoblog.spider_notify.baidu_notify:15 spider_notify] {"remain":5,"success":0,"not_same_site":["https://example.com/article/2025/9/16/6.html"]} +[2025-09-16 19:43:40,529] INFO [djangoblog.spider_notify.baidu_notify:15 spider_notify] {"remain":5,"success":0,"not_same_site":["https://example.com/article/2025/9/16/7.html"]} +[2025-09-16 19:43:40,529] INFO [djangoblog.spider_notify.baidu_notify:15 spider_notify] {"remain":5,"success":0,"not_same_site":["https://example.com/article/2025/9/16/7.html"]} +[2025-09-16 19:43:40,733] INFO [djangoblog.spider_notify.baidu_notify:15 spider_notify] {"remain":5,"success":0,"not_same_site":["https://example.com/tag/biao-qian-7.html"]} +[2025-09-16 19:43:40,733] INFO [djangoblog.spider_notify.baidu_notify:15 spider_notify] {"remain":5,"success":0,"not_same_site":["https://example.com/tag/biao-qian-7.html"]} +[2025-09-16 19:43:40,838] INFO [djangoblog.spider_notify.baidu_notify:15 spider_notify] {"remain":5,"success":0,"not_same_site":["https://example.com/article/2025/9/16/7.html"]} +[2025-09-16 19:43:40,838] INFO [djangoblog.spider_notify.baidu_notify:15 spider_notify] {"remain":5,"success":0,"not_same_site":["https://example.com/article/2025/9/16/7.html"]} +[2025-09-16 19:43:40,944] INFO [djangoblog.spider_notify.baidu_notify:15 spider_notify] {"remain":5,"success":0,"not_same_site":["https://example.com/article/2025/9/16/8.html"]} +[2025-09-16 19:43:40,944] INFO [djangoblog.spider_notify.baidu_notify:15 spider_notify] {"remain":5,"success":0,"not_same_site":["https://example.com/article/2025/9/16/8.html"]} +[2025-09-16 19:43:41,093] INFO [djangoblog.spider_notify.baidu_notify:15 spider_notify] {"remain":5,"success":0,"not_same_site":["https://example.com/tag/biao-qian-8.html"]} +[2025-09-16 19:43:41,093] INFO [djangoblog.spider_notify.baidu_notify:15 spider_notify] {"remain":5,"success":0,"not_same_site":["https://example.com/tag/biao-qian-8.html"]} +[2025-09-16 19:43:41,450] INFO [djangoblog.spider_notify.baidu_notify:15 spider_notify] {"remain":5,"success":0,"not_same_site":["https://example.com/article/2025/9/16/8.html"]} +[2025-09-16 19:43:41,450] INFO [djangoblog.spider_notify.baidu_notify:15 spider_notify] {"remain":5,"success":0,"not_same_site":["https://example.com/article/2025/9/16/8.html"]} +[2025-09-16 19:43:41,625] INFO [djangoblog.spider_notify.baidu_notify:15 spider_notify] {"remain":5,"success":0,"not_same_site":["https://example.com/article/2025/9/16/9.html"]} +[2025-09-16 19:43:41,625] INFO [djangoblog.spider_notify.baidu_notify:15 spider_notify] {"remain":5,"success":0,"not_same_site":["https://example.com/article/2025/9/16/9.html"]} +[2025-09-16 19:43:41,860] INFO [djangoblog.spider_notify.baidu_notify:15 spider_notify] {"remain":5,"success":0,"not_same_site":["https://example.com/tag/biao-qian-9.html"]} +[2025-09-16 19:43:41,860] INFO [djangoblog.spider_notify.baidu_notify:15 spider_notify] {"remain":5,"success":0,"not_same_site":["https://example.com/tag/biao-qian-9.html"]} +[2025-09-16 19:43:42,168] INFO [djangoblog.spider_notify.baidu_notify:15 spider_notify] {"remain":5,"success":0,"not_same_site":["https://example.com/article/2025/9/16/9.html"]} +[2025-09-16 19:43:42,168] INFO [djangoblog.spider_notify.baidu_notify:15 spider_notify] {"remain":5,"success":0,"not_same_site":["https://example.com/article/2025/9/16/9.html"]} +[2025-09-16 19:43:42,473] INFO [djangoblog.spider_notify.baidu_notify:15 spider_notify] {"remain":5,"success":0,"not_same_site":["https://example.com/article/2025/9/16/10.html"]} +[2025-09-16 19:43:42,473] INFO [djangoblog.spider_notify.baidu_notify:15 spider_notify] {"remain":5,"success":0,"not_same_site":["https://example.com/article/2025/9/16/10.html"]} +[2025-09-16 19:43:42,619] INFO [djangoblog.spider_notify.baidu_notify:15 spider_notify] {"remain":5,"success":0,"not_same_site":["https://example.com/tag/biao-qian-10.html"]} +[2025-09-16 19:43:42,619] INFO [djangoblog.spider_notify.baidu_notify:15 spider_notify] {"remain":5,"success":0,"not_same_site":["https://example.com/tag/biao-qian-10.html"]} +[2025-09-16 19:43:42,991] INFO [djangoblog.spider_notify.baidu_notify:15 spider_notify] {"remain":5,"success":0,"not_same_site":["https://example.com/article/2025/9/16/10.html"]} +[2025-09-16 19:43:42,991] INFO [djangoblog.spider_notify.baidu_notify:15 spider_notify] {"remain":5,"success":0,"not_same_site":["https://example.com/article/2025/9/16/10.html"]} +[2025-09-16 19:43:43,301] INFO [djangoblog.spider_notify.baidu_notify:15 spider_notify] {"remain":5,"success":0,"not_same_site":["https://example.com/article/2025/9/16/11.html"]} +[2025-09-16 19:43:43,301] INFO [djangoblog.spider_notify.baidu_notify:15 spider_notify] {"remain":5,"success":0,"not_same_site":["https://example.com/article/2025/9/16/11.html"]} +[2025-09-16 19:43:43,601] INFO [djangoblog.spider_notify.baidu_notify:15 spider_notify] {"remain":5,"success":0,"not_same_site":["https://example.com/tag/biao-qian-11.html"]} +[2025-09-16 19:43:43,601] INFO [djangoblog.spider_notify.baidu_notify:15 spider_notify] {"remain":5,"success":0,"not_same_site":["https://example.com/tag/biao-qian-11.html"]} +[2025-09-16 19:43:43,785] INFO [djangoblog.spider_notify.baidu_notify:15 spider_notify] {"remain":5,"success":0,"not_same_site":["https://example.com/article/2025/9/16/11.html"]} +[2025-09-16 19:43:43,785] INFO [djangoblog.spider_notify.baidu_notify:15 spider_notify] {"remain":5,"success":0,"not_same_site":["https://example.com/article/2025/9/16/11.html"]} +[2025-09-16 19:43:44,036] INFO [djangoblog.spider_notify.baidu_notify:15 spider_notify] {"remain":5,"success":0,"not_same_site":["https://example.com/article/2025/9/16/12.html"]} +[2025-09-16 19:43:44,036] INFO [djangoblog.spider_notify.baidu_notify:15 spider_notify] {"remain":5,"success":0,"not_same_site":["https://example.com/article/2025/9/16/12.html"]} +[2025-09-16 19:43:44,318] INFO [djangoblog.spider_notify.baidu_notify:15 spider_notify] {"remain":5,"success":0,"not_same_site":["https://example.com/tag/biao-qian-12.html"]} +[2025-09-16 19:43:44,318] INFO [djangoblog.spider_notify.baidu_notify:15 spider_notify] {"remain":5,"success":0,"not_same_site":["https://example.com/tag/biao-qian-12.html"]} +[2025-09-16 19:43:44,432] INFO [djangoblog.spider_notify.baidu_notify:15 spider_notify] {"remain":5,"success":0,"not_same_site":["https://example.com/article/2025/9/16/12.html"]} +[2025-09-16 19:43:44,432] INFO [djangoblog.spider_notify.baidu_notify:15 spider_notify] {"remain":5,"success":0,"not_same_site":["https://example.com/article/2025/9/16/12.html"]} +[2025-09-16 19:43:44,726] INFO [djangoblog.spider_notify.baidu_notify:15 spider_notify] {"remain":5,"success":0,"not_same_site":["https://example.com/article/2025/9/16/13.html"]} +[2025-09-16 19:43:44,726] INFO [djangoblog.spider_notify.baidu_notify:15 spider_notify] {"remain":5,"success":0,"not_same_site":["https://example.com/article/2025/9/16/13.html"]} +[2025-09-16 19:43:44,983] INFO [djangoblog.spider_notify.baidu_notify:15 spider_notify] {"remain":5,"success":0,"not_same_site":["https://example.com/tag/biao-qian-13.html"]} +[2025-09-16 19:43:44,983] INFO [djangoblog.spider_notify.baidu_notify:15 spider_notify] {"remain":5,"success":0,"not_same_site":["https://example.com/tag/biao-qian-13.html"]} +[2025-09-16 19:43:45,300] INFO [djangoblog.spider_notify.baidu_notify:15 spider_notify] {"remain":5,"success":0,"not_same_site":["https://example.com/article/2025/9/16/13.html"]} +[2025-09-16 19:43:45,300] INFO [djangoblog.spider_notify.baidu_notify:15 spider_notify] {"remain":5,"success":0,"not_same_site":["https://example.com/article/2025/9/16/13.html"]} +[2025-09-16 19:43:45,472] INFO [djangoblog.spider_notify.baidu_notify:15 spider_notify] {"remain":5,"success":0,"not_same_site":["https://example.com/article/2025/9/16/14.html"]} +[2025-09-16 19:43:45,472] INFO [djangoblog.spider_notify.baidu_notify:15 spider_notify] {"remain":5,"success":0,"not_same_site":["https://example.com/article/2025/9/16/14.html"]} +[2025-09-16 19:43:45,563] INFO [djangoblog.spider_notify.baidu_notify:15 spider_notify] {"remain":5,"success":0,"not_same_site":["https://example.com/tag/biao-qian-14.html"]} +[2025-09-16 19:43:45,563] INFO [djangoblog.spider_notify.baidu_notify:15 spider_notify] {"remain":5,"success":0,"not_same_site":["https://example.com/tag/biao-qian-14.html"]} +[2025-09-16 19:43:45,687] INFO [djangoblog.spider_notify.baidu_notify:15 spider_notify] {"remain":5,"success":0,"not_same_site":["https://example.com/article/2025/9/16/14.html"]} +[2025-09-16 19:43:45,687] INFO [djangoblog.spider_notify.baidu_notify:15 spider_notify] {"remain":5,"success":0,"not_same_site":["https://example.com/article/2025/9/16/14.html"]} +[2025-09-16 19:43:46,060] INFO [djangoblog.spider_notify.baidu_notify:15 spider_notify] {"remain":5,"success":0,"not_same_site":["https://example.com/article/2025/9/16/15.html"]} +[2025-09-16 19:43:46,060] INFO [djangoblog.spider_notify.baidu_notify:15 spider_notify] {"remain":5,"success":0,"not_same_site":["https://example.com/article/2025/9/16/15.html"]} +[2025-09-16 19:43:46,204] INFO [djangoblog.spider_notify.baidu_notify:15 spider_notify] {"remain":5,"success":0,"not_same_site":["https://example.com/tag/biao-qian-15.html"]} +[2025-09-16 19:43:46,204] INFO [djangoblog.spider_notify.baidu_notify:15 spider_notify] {"remain":5,"success":0,"not_same_site":["https://example.com/tag/biao-qian-15.html"]} +[2025-09-16 19:43:46,321] INFO [djangoblog.spider_notify.baidu_notify:15 spider_notify] {"remain":5,"success":0,"not_same_site":["https://example.com/article/2025/9/16/15.html"]} +[2025-09-16 19:43:46,321] INFO [djangoblog.spider_notify.baidu_notify:15 spider_notify] {"remain":5,"success":0,"not_same_site":["https://example.com/article/2025/9/16/15.html"]} +[2025-09-16 19:43:46,499] INFO [djangoblog.spider_notify.baidu_notify:15 spider_notify] {"remain":5,"success":0,"not_same_site":["https://example.com/article/2025/9/16/16.html"]} +[2025-09-16 19:43:46,499] INFO [djangoblog.spider_notify.baidu_notify:15 spider_notify] {"remain":5,"success":0,"not_same_site":["https://example.com/article/2025/9/16/16.html"]} +[2025-09-16 19:43:46,879] INFO [djangoblog.spider_notify.baidu_notify:15 spider_notify] {"remain":5,"success":0,"not_same_site":["https://example.com/tag/biao-qian-16.html"]} +[2025-09-16 19:43:46,879] INFO [djangoblog.spider_notify.baidu_notify:15 spider_notify] {"remain":5,"success":0,"not_same_site":["https://example.com/tag/biao-qian-16.html"]} +[2025-09-16 19:43:46,994] INFO [djangoblog.spider_notify.baidu_notify:15 spider_notify] {"remain":5,"success":0,"not_same_site":["https://example.com/article/2025/9/16/16.html"]} +[2025-09-16 19:43:46,994] INFO [djangoblog.spider_notify.baidu_notify:15 spider_notify] {"remain":5,"success":0,"not_same_site":["https://example.com/article/2025/9/16/16.html"]} +[2025-09-16 19:43:47,291] INFO [djangoblog.spider_notify.baidu_notify:15 spider_notify] {"remain":5,"success":0,"not_same_site":["https://example.com/article/2025/9/16/17.html"]} +[2025-09-16 19:43:47,291] INFO [djangoblog.spider_notify.baidu_notify:15 spider_notify] {"remain":5,"success":0,"not_same_site":["https://example.com/article/2025/9/16/17.html"]} +[2025-09-16 19:43:47,444] INFO [djangoblog.spider_notify.baidu_notify:15 spider_notify] {"remain":5,"success":0,"not_same_site":["https://example.com/tag/biao-qian-17.html"]} +[2025-09-16 19:43:47,444] INFO [djangoblog.spider_notify.baidu_notify:15 spider_notify] {"remain":5,"success":0,"not_same_site":["https://example.com/tag/biao-qian-17.html"]} +[2025-09-16 19:43:47,801] INFO [djangoblog.spider_notify.baidu_notify:15 spider_notify] {"remain":5,"success":0,"not_same_site":["https://example.com/article/2025/9/16/17.html"]} +[2025-09-16 19:43:47,801] INFO [djangoblog.spider_notify.baidu_notify:15 spider_notify] {"remain":5,"success":0,"not_same_site":["https://example.com/article/2025/9/16/17.html"]} +[2025-09-16 19:43:48,106] INFO [djangoblog.spider_notify.baidu_notify:15 spider_notify] {"remain":5,"success":0,"not_same_site":["https://example.com/article/2025/9/16/18.html"]} +[2025-09-16 19:43:48,106] INFO [djangoblog.spider_notify.baidu_notify:15 spider_notify] {"remain":5,"success":0,"not_same_site":["https://example.com/article/2025/9/16/18.html"]} +[2025-09-16 19:43:48,309] INFO [djangoblog.spider_notify.baidu_notify:15 spider_notify] {"remain":5,"success":0,"not_same_site":["https://example.com/tag/biao-qian-18.html"]} +[2025-09-16 19:43:48,309] INFO [djangoblog.spider_notify.baidu_notify:15 spider_notify] {"remain":5,"success":0,"not_same_site":["https://example.com/tag/biao-qian-18.html"]} +[2025-09-16 19:43:48,454] INFO [djangoblog.spider_notify.baidu_notify:15 spider_notify] {"remain":5,"success":0,"not_same_site":["https://example.com/article/2025/9/16/18.html"]} +[2025-09-16 19:43:48,454] INFO [djangoblog.spider_notify.baidu_notify:15 spider_notify] {"remain":5,"success":0,"not_same_site":["https://example.com/article/2025/9/16/18.html"]} +[2025-09-16 19:43:48,576] INFO [djangoblog.spider_notify.baidu_notify:15 spider_notify] {"remain":5,"success":0,"not_same_site":["https://example.com/article/2025/9/16/19.html"]} +[2025-09-16 19:43:48,576] INFO [djangoblog.spider_notify.baidu_notify:15 spider_notify] {"remain":5,"success":0,"not_same_site":["https://example.com/article/2025/9/16/19.html"]} +[2025-09-16 19:43:48,734] INFO [djangoblog.spider_notify.baidu_notify:15 spider_notify] {"remain":5,"success":0,"not_same_site":["https://example.com/tag/biao-qian-19.html"]} +[2025-09-16 19:43:48,734] INFO [djangoblog.spider_notify.baidu_notify:15 spider_notify] {"remain":5,"success":0,"not_same_site":["https://example.com/tag/biao-qian-19.html"]} +[2025-09-16 19:43:49,027] INFO [djangoblog.spider_notify.baidu_notify:15 spider_notify] {"remain":5,"success":0,"not_same_site":["https://example.com/article/2025/9/16/19.html"]} +[2025-09-16 19:43:49,027] INFO [djangoblog.spider_notify.baidu_notify:15 spider_notify] {"remain":5,"success":0,"not_same_site":["https://example.com/article/2025/9/16/19.html"]} +[2025-09-16 19:45:10,563] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] 文章结尾版权声明 initialized. +[2025-09-16 19:45:10,563] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] 文章结尾版权声明 initialized. +[2025-09-16 19:45:10,563] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: article_copyright +[2025-09-16 19:45:10,563] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: article_copyright +[2025-09-16 19:45:10,565] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] 阅读时间预测 initialized. +[2025-09-16 19:45:10,565] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] 阅读时间预测 initialized. +[2025-09-16 19:45:10,566] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: reading_time +[2025-09-16 19:45:10,566] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: reading_time +[2025-09-16 19:45:10,568] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] 外部链接处理器 initialized. +[2025-09-16 19:45:10,568] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] 外部链接处理器 initialized. +[2025-09-16 19:45:10,568] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: external_links +[2025-09-16 19:45:10,568] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: external_links +[2025-09-16 19:45:10,569] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] 文章浏览次数统计 initialized. +[2025-09-16 19:45:10,569] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] 文章浏览次数统计 initialized. +[2025-09-16 19:45:10,569] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: view_count +[2025-09-16 19:45:10,569] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: view_count +[2025-09-16 19:45:10,571] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] SEO 优化器 initialized. +[2025-09-16 19:45:10,571] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] SEO 优化器 initialized. +[2025-09-16 19:45:10,571] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: seo_optimizer +[2025-09-16 19:45:10,571] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: seo_optimizer +[2025-09-16 19:45:12,551] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] 文章结尾版权声明 initialized. +[2025-09-16 19:45:12,551] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] 文章结尾版权声明 initialized. +[2025-09-16 19:45:12,551] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: article_copyright +[2025-09-16 19:45:12,551] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: article_copyright +[2025-09-16 19:45:12,554] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] 阅读时间预测 initialized. +[2025-09-16 19:45:12,554] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] 阅读时间预测 initialized. +[2025-09-16 19:45:12,554] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: reading_time +[2025-09-16 19:45:12,554] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: reading_time +[2025-09-16 19:45:12,557] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] 外部链接处理器 initialized. +[2025-09-16 19:45:12,557] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] 外部链接处理器 initialized. +[2025-09-16 19:45:12,557] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: external_links +[2025-09-16 19:45:12,557] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: external_links +[2025-09-16 19:45:12,558] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] 文章浏览次数统计 initialized. +[2025-09-16 19:45:12,558] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] 文章浏览次数统计 initialized. +[2025-09-16 19:45:12,558] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: view_count +[2025-09-16 19:45:12,558] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: view_count +[2025-09-16 19:45:12,560] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] SEO 优化器 initialized. +[2025-09-16 19:45:12,560] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] SEO 优化器 initialized. +[2025-09-16 19:45:12,560] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: seo_optimizer +[2025-09-16 19:45:12,560] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: seo_optimizer +[2025-09-16 19:45:12,600] INFO [django.utils.autoreload.run_with_reloader:667 autoreload] Watching for file changes with StatReloader +[2025-09-16 19:45:16,716] INFO [blog.views.get_queryset_from_cache:75 views] set view cache.key:index_1 +[2025-09-16 19:45:16,732] INFO [blog.context_processors.seo_processor:17 context_processors] set processor cache. +[2025-09-16 19:45:16,776] INFO [djangoblog.utils.get_blog_setting:171 utils] set cache get_blog_setting +[2025-09-16 19:45:16,776] INFO [djangoblog.utils.get_blog_setting:171 utils] set cache get_blog_setting +[2025-09-16 19:45:17,044] INFO [blog.templatetags.blog_tags.load_sidebar:145 blog_tags] load sidebar +[2025-09-16 19:45:17,103] INFO [blog.templatetags.blog_tags.load_sidebar:189 blog_tags] set sidebar cache.key:sidebari +[2025-09-16 19:45:25,821] INFO [blog.views.get_queryset_from_cache:75 views] set view cache.key:index_1 +[2025-09-16 20:42:43,144] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] 文章结尾版权声明 initialized. +[2025-09-16 20:42:43,144] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] 文章结尾版权声明 initialized. +[2025-09-16 20:42:43,145] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: article_copyright +[2025-09-16 20:42:43,145] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: article_copyright +[2025-09-16 20:42:43,146] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] 阅读时间预测 initialized. +[2025-09-16 20:42:43,146] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] 阅读时间预测 initialized. +[2025-09-16 20:42:43,146] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: reading_time +[2025-09-16 20:42:43,146] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: reading_time +[2025-09-16 20:42:43,147] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] 外部链接处理器 initialized. +[2025-09-16 20:42:43,147] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] 外部链接处理器 initialized. +[2025-09-16 20:42:43,148] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: external_links +[2025-09-16 20:42:43,148] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: external_links +[2025-09-16 20:42:43,149] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] 文章浏览次数统计 initialized. +[2025-09-16 20:42:43,149] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] 文章浏览次数统计 initialized. +[2025-09-16 20:42:43,149] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: view_count +[2025-09-16 20:42:43,149] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: view_count +[2025-09-16 20:42:43,150] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] SEO 优化器 initialized. +[2025-09-16 20:42:43,150] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] SEO 优化器 initialized. +[2025-09-16 20:42:43,151] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: seo_optimizer +[2025-09-16 20:42:43,151] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: seo_optimizer +[2025-09-16 20:42:44,148] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] 文章结尾版权声明 initialized. +[2025-09-16 20:42:44,148] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] 文章结尾版权声明 initialized. +[2025-09-16 20:42:44,148] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: article_copyright +[2025-09-16 20:42:44,148] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: article_copyright +[2025-09-16 20:42:44,150] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] 阅读时间预测 initialized. +[2025-09-16 20:42:44,150] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] 阅读时间预测 initialized. +[2025-09-16 20:42:44,150] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: reading_time +[2025-09-16 20:42:44,150] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: reading_time +[2025-09-16 20:42:44,151] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] 外部链接处理器 initialized. +[2025-09-16 20:42:44,151] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] 外部链接处理器 initialized. +[2025-09-16 20:42:44,151] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: external_links +[2025-09-16 20:42:44,151] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: external_links +[2025-09-16 20:42:44,152] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] 文章浏览次数统计 initialized. +[2025-09-16 20:42:44,152] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] 文章浏览次数统计 initialized. +[2025-09-16 20:42:44,152] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: view_count +[2025-09-16 20:42:44,152] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: view_count +[2025-09-16 20:42:44,153] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] SEO 优化器 initialized. +[2025-09-16 20:42:44,153] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] SEO 优化器 initialized. +[2025-09-16 20:42:44,154] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: seo_optimizer +[2025-09-16 20:42:44,154] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: seo_optimizer +[2025-09-16 20:42:44,182] INFO [django.utils.autoreload.run_with_reloader:667 autoreload] Watching for file changes with StatReloader diff --git a/src/DjangoBlog/logs/djangoblog.log.2025-09-22 b/src/DjangoBlog/logs/djangoblog.log.2025-09-22 new file mode 100644 index 0000000..7d5d54a --- /dev/null +++ b/src/DjangoBlog/logs/djangoblog.log.2025-09-22 @@ -0,0 +1,52 @@ +[2025-09-22 19:38:03,065] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] 文章结尾版权声明 initialized. +[2025-09-22 19:38:03,065] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] 文章结尾版权声明 initialized. +[2025-09-22 19:38:03,066] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: article_copyright +[2025-09-22 19:38:03,066] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: article_copyright +[2025-09-22 19:38:03,069] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] 阅读时间预测 initialized. +[2025-09-22 19:38:03,069] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] 阅读时间预测 initialized. +[2025-09-22 19:38:03,069] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: reading_time +[2025-09-22 19:38:03,069] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: reading_time +[2025-09-22 19:38:03,071] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] 外部链接处理器 initialized. +[2025-09-22 19:38:03,071] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] 外部链接处理器 initialized. +[2025-09-22 19:38:03,071] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: external_links +[2025-09-22 19:38:03,071] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: external_links +[2025-09-22 19:38:03,073] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] 文章浏览次数统计 initialized. +[2025-09-22 19:38:03,073] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] 文章浏览次数统计 initialized. +[2025-09-22 19:38:03,073] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: view_count +[2025-09-22 19:38:03,073] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: view_count +[2025-09-22 19:38:03,075] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] SEO 优化器 initialized. +[2025-09-22 19:38:03,075] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] SEO 优化器 initialized. +[2025-09-22 19:38:03,075] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: seo_optimizer +[2025-09-22 19:38:03,075] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: seo_optimizer +[2025-09-22 19:38:04,061] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] 文章结尾版权声明 initialized. +[2025-09-22 19:38:04,061] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] 文章结尾版权声明 initialized. +[2025-09-22 19:38:04,061] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: article_copyright +[2025-09-22 19:38:04,061] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: article_copyright +[2025-09-22 19:38:04,062] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] 阅读时间预测 initialized. +[2025-09-22 19:38:04,062] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] 阅读时间预测 initialized. +[2025-09-22 19:38:04,062] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: reading_time +[2025-09-22 19:38:04,062] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: reading_time +[2025-09-22 19:38:04,064] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] 外部链接处理器 initialized. +[2025-09-22 19:38:04,064] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] 外部链接处理器 initialized. +[2025-09-22 19:38:04,064] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: external_links +[2025-09-22 19:38:04,064] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: external_links +[2025-09-22 19:38:04,065] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] 文章浏览次数统计 initialized. +[2025-09-22 19:38:04,065] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] 文章浏览次数统计 initialized. +[2025-09-22 19:38:04,066] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: view_count +[2025-09-22 19:38:04,066] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: view_count +[2025-09-22 19:38:04,068] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] SEO 优化器 initialized. +[2025-09-22 19:38:04,068] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] SEO 优化器 initialized. +[2025-09-22 19:38:04,069] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: seo_optimizer +[2025-09-22 19:38:04,069] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: seo_optimizer +[2025-09-22 19:38:04,093] INFO [django.utils.autoreload.run_with_reloader:667 autoreload] Watching for file changes with StatReloader +[2025-09-22 19:38:07,640] INFO [blog.views.get_queryset_from_cache:75 views] set view cache.key:index_1 +[2025-09-22 19:38:07,647] INFO [blog.context_processors.seo_processor:17 context_processors] set processor cache. +[2025-09-22 19:38:07,670] INFO [djangoblog.utils.get_blog_setting:171 utils] set cache get_blog_setting +[2025-09-22 19:38:07,670] INFO [djangoblog.utils.get_blog_setting:171 utils] set cache get_blog_setting +[2025-09-22 19:38:08,051] INFO [blog.templatetags.blog_tags.load_sidebar:145 blog_tags] load sidebar +[2025-09-22 19:38:08,098] INFO [blog.templatetags.blog_tags.load_sidebar:189 blog_tags] set sidebar cache.key:sidebari +[2025-09-22 20:44:15,619] INFO [blog.views.get_queryset_from_cache:70 views] get view cache.key:index_1 +[2025-09-22 20:45:41,342] INFO [blog.views.get_queryset_from_cache:70 views] get view cache.key:index_1 +[2025-09-22 20:49:59,696] INFO [blog.models.comment_list:151 models] set article comments:19 +[2025-09-22 20:49:59,776] INFO [blog.templatetags.blog_tags.load_sidebar:145 blog_tags] load sidebar +[2025-09-22 20:49:59,786] INFO [blog.templatetags.blog_tags.load_sidebar:189 blog_tags] set sidebar cache.key:sidebarp diff --git a/src/DjangoBlog/logs/djangoblog.log.2025-09-23 b/src/DjangoBlog/logs/djangoblog.log.2025-09-23 new file mode 100644 index 0000000..704a801 --- /dev/null +++ b/src/DjangoBlog/logs/djangoblog.log.2025-09-23 @@ -0,0 +1,47 @@ +[2025-09-23 21:13:41,768] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] 文章结尾版权声明 initialized. +[2025-09-23 21:13:41,768] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] 文章结尾版权声明 initialized. +[2025-09-23 21:13:41,769] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: article_copyright +[2025-09-23 21:13:41,769] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: article_copyright +[2025-09-23 21:13:41,772] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] 阅读时间预测 initialized. +[2025-09-23 21:13:41,772] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] 阅读时间预测 initialized. +[2025-09-23 21:13:41,773] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: reading_time +[2025-09-23 21:13:41,773] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: reading_time +[2025-09-23 21:13:41,775] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] 外部链接处理器 initialized. +[2025-09-23 21:13:41,775] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] 外部链接处理器 initialized. +[2025-09-23 21:13:41,775] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: external_links +[2025-09-23 21:13:41,775] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: external_links +[2025-09-23 21:13:41,778] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] 文章浏览次数统计 initialized. +[2025-09-23 21:13:41,778] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] 文章浏览次数统计 initialized. +[2025-09-23 21:13:41,778] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: view_count +[2025-09-23 21:13:41,778] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: view_count +[2025-09-23 21:13:41,781] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] SEO 优化器 initialized. +[2025-09-23 21:13:41,781] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] SEO 优化器 initialized. +[2025-09-23 21:13:41,782] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: seo_optimizer +[2025-09-23 21:13:41,782] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: seo_optimizer +[2025-09-23 21:13:43,479] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] 文章结尾版权声明 initialized. +[2025-09-23 21:13:43,479] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] 文章结尾版权声明 initialized. +[2025-09-23 21:13:43,480] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: article_copyright +[2025-09-23 21:13:43,480] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: article_copyright +[2025-09-23 21:13:43,482] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] 阅读时间预测 initialized. +[2025-09-23 21:13:43,482] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] 阅读时间预测 initialized. +[2025-09-23 21:13:43,482] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: reading_time +[2025-09-23 21:13:43,482] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: reading_time +[2025-09-23 21:13:43,484] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] 外部链接处理器 initialized. +[2025-09-23 21:13:43,484] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] 外部链接处理器 initialized. +[2025-09-23 21:13:43,484] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: external_links +[2025-09-23 21:13:43,484] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: external_links +[2025-09-23 21:13:43,485] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] 文章浏览次数统计 initialized. +[2025-09-23 21:13:43,485] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] 文章浏览次数统计 initialized. +[2025-09-23 21:13:43,486] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: view_count +[2025-09-23 21:13:43,486] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: view_count +[2025-09-23 21:13:43,487] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] SEO 优化器 initialized. +[2025-09-23 21:13:43,487] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] SEO 优化器 initialized. +[2025-09-23 21:13:43,488] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: seo_optimizer +[2025-09-23 21:13:43,488] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: seo_optimizer +[2025-09-23 21:13:43,521] INFO [django.utils.autoreload.run_with_reloader:667 autoreload] Watching for file changes with StatReloader +[2025-09-23 21:13:49,494] INFO [blog.views.get_queryset_from_cache:75 views] set view cache.key:index_1 +[2025-09-23 21:13:49,505] INFO [blog.context_processors.seo_processor:17 context_processors] set processor cache. +[2025-09-23 21:13:49,530] INFO [djangoblog.utils.get_blog_setting:171 utils] set cache get_blog_setting +[2025-09-23 21:13:49,530] INFO [djangoblog.utils.get_blog_setting:171 utils] set cache get_blog_setting +[2025-09-23 21:13:50,166] INFO [blog.templatetags.blog_tags.load_sidebar:145 blog_tags] load sidebar +[2025-09-23 21:13:50,271] INFO [blog.templatetags.blog_tags.load_sidebar:189 blog_tags] set sidebar cache.key:sidebari diff --git a/src/DjangoBlog/logs/djangoblog.log.2025-10-09 b/src/DjangoBlog/logs/djangoblog.log.2025-10-09 new file mode 100644 index 0000000..b6e1d91 --- /dev/null +++ b/src/DjangoBlog/logs/djangoblog.log.2025-10-09 @@ -0,0 +1,378 @@ +[2025-10-09 20:20:40,552] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] 文章结尾版权声明 initialized. +[2025-10-09 20:20:40,552] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] 文章结尾版权声明 initialized. +[2025-10-09 20:20:40,556] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: article_copyright +[2025-10-09 20:20:40,556] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: article_copyright +[2025-10-09 20:20:40,563] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] 阅读时间预测 initialized. +[2025-10-09 20:20:40,563] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] 阅读时间预测 initialized. +[2025-10-09 20:20:40,564] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: reading_time +[2025-10-09 20:20:40,564] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: reading_time +[2025-10-09 20:20:40,569] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] 外部链接处理器 initialized. +[2025-10-09 20:20:40,569] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] 外部链接处理器 initialized. +[2025-10-09 20:20:40,570] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: external_links +[2025-10-09 20:20:40,570] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: external_links +[2025-10-09 20:20:40,576] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] 文章浏览次数统计 initialized. +[2025-10-09 20:20:40,576] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] 文章浏览次数统计 initialized. +[2025-10-09 20:20:40,576] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: view_count +[2025-10-09 20:20:40,576] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: view_count +[2025-10-09 20:20:40,582] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] SEO 优化器 initialized. +[2025-10-09 20:20:40,582] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] SEO 优化器 initialized. +[2025-10-09 20:20:40,582] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: seo_optimizer +[2025-10-09 20:20:40,582] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: seo_optimizer +[2025-10-09 20:20:44,018] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] 文章结尾版权声明 initialized. +[2025-10-09 20:20:44,018] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] 文章结尾版权声明 initialized. +[2025-10-09 20:20:44,019] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: article_copyright +[2025-10-09 20:20:44,019] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: article_copyright +[2025-10-09 20:20:44,022] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] 阅读时间预测 initialized. +[2025-10-09 20:20:44,022] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] 阅读时间预测 initialized. +[2025-10-09 20:20:44,022] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: reading_time +[2025-10-09 20:20:44,022] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: reading_time +[2025-10-09 20:20:44,024] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] 外部链接处理器 initialized. +[2025-10-09 20:20:44,024] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] 外部链接处理器 initialized. +[2025-10-09 20:20:44,025] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: external_links +[2025-10-09 20:20:44,025] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: external_links +[2025-10-09 20:20:44,027] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] 文章浏览次数统计 initialized. +[2025-10-09 20:20:44,027] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] 文章浏览次数统计 initialized. +[2025-10-09 20:20:44,028] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: view_count +[2025-10-09 20:20:44,028] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: view_count +[2025-10-09 20:20:44,031] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] SEO 优化器 initialized. +[2025-10-09 20:20:44,031] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] SEO 优化器 initialized. +[2025-10-09 20:20:44,031] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: seo_optimizer +[2025-10-09 20:20:44,031] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: seo_optimizer +[2025-10-09 20:20:44,086] INFO [django.utils.autoreload.run_with_reloader:667 autoreload] Watching for file changes with StatReloader +[2025-10-09 20:20:49,912] INFO [blog.views.get_queryset_from_cache:75 views] set view cache.key:index_1 +[2025-10-09 20:20:50,270] INFO [blog.context_processors.seo_processor:17 context_processors] set processor cache. +[2025-10-09 20:20:50,389] INFO [djangoblog.utils.get_blog_setting:171 utils] set cache get_blog_setting +[2025-10-09 20:20:50,389] INFO [djangoblog.utils.get_blog_setting:171 utils] set cache get_blog_setting +[2025-10-09 20:20:53,612] INFO [blog.templatetags.blog_tags.load_sidebar:145 blog_tags] load sidebar +[2025-10-09 20:20:54,150] INFO [blog.templatetags.blog_tags.load_sidebar:189 blog_tags] set sidebar cache.key:sidebari +[2025-10-09 20:20:59,864] INFO [blog.models.comment_list:151 models] set article comments:19 +[2025-10-09 20:21:00,356] INFO [blog.templatetags.blog_tags.load_sidebar:145 blog_tags] load sidebar +[2025-10-09 20:21:00,411] INFO [blog.templatetags.blog_tags.load_sidebar:189 blog_tags] set sidebar cache.key:sidebarp +[2025-10-09 20:21:00,566] INFO [blog.models.comment_list:151 models] set article comments:19 +[2025-10-09 20:21:00,811] INFO [blog.models.comment_list:151 models] set article comments:19 +[2025-10-09 20:21:00,976] INFO [blog.models.comment_list:151 models] set article comments:19 +[2025-10-09 20:23:43,991] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] 文章结尾版权声明 initialized. +[2025-10-09 20:23:43,991] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] 文章结尾版权声明 initialized. +[2025-10-09 20:23:43,992] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: article_copyright +[2025-10-09 20:23:43,992] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: article_copyright +[2025-10-09 20:23:43,993] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] 阅读时间预测 initialized. +[2025-10-09 20:23:43,993] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] 阅读时间预测 initialized. +[2025-10-09 20:23:43,993] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: reading_time +[2025-10-09 20:23:43,993] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: reading_time +[2025-10-09 20:23:43,994] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] 外部链接处理器 initialized. +[2025-10-09 20:23:43,994] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] 外部链接处理器 initialized. +[2025-10-09 20:23:43,994] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: external_links +[2025-10-09 20:23:43,994] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: external_links +[2025-10-09 20:23:43,997] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] 文章浏览次数统计 initialized. +[2025-10-09 20:23:43,997] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] 文章浏览次数统计 initialized. +[2025-10-09 20:23:43,997] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: view_count +[2025-10-09 20:23:43,997] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: view_count +[2025-10-09 20:23:43,999] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] SEO 优化器 initialized. +[2025-10-09 20:23:43,999] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] SEO 优化器 initialized. +[2025-10-09 20:23:44,002] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: seo_optimizer +[2025-10-09 20:23:44,002] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: seo_optimizer +[2025-10-09 20:23:59,019] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] 文章结尾版权声明 initialized. +[2025-10-09 20:23:59,019] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] 文章结尾版权声明 initialized. +[2025-10-09 20:23:59,019] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: article_copyright +[2025-10-09 20:23:59,019] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: article_copyright +[2025-10-09 20:23:59,021] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] 阅读时间预测 initialized. +[2025-10-09 20:23:59,021] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] 阅读时间预测 initialized. +[2025-10-09 20:23:59,021] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: reading_time +[2025-10-09 20:23:59,021] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: reading_time +[2025-10-09 20:23:59,023] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] 外部链接处理器 initialized. +[2025-10-09 20:23:59,023] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] 外部链接处理器 initialized. +[2025-10-09 20:23:59,023] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: external_links +[2025-10-09 20:23:59,023] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: external_links +[2025-10-09 20:23:59,025] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] 文章浏览次数统计 initialized. +[2025-10-09 20:23:59,025] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] 文章浏览次数统计 initialized. +[2025-10-09 20:23:59,025] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: view_count +[2025-10-09 20:23:59,025] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: view_count +[2025-10-09 20:23:59,027] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] SEO 优化器 initialized. +[2025-10-09 20:23:59,027] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] SEO 优化器 initialized. +[2025-10-09 20:23:59,027] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: seo_optimizer +[2025-10-09 20:23:59,027] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: seo_optimizer +[2025-10-09 20:24:18,503] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] 文章结尾版权声明 initialized. +[2025-10-09 20:24:18,503] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] 文章结尾版权声明 initialized. +[2025-10-09 20:24:18,503] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: article_copyright +[2025-10-09 20:24:18,503] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: article_copyright +[2025-10-09 20:24:18,506] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] 阅读时间预测 initialized. +[2025-10-09 20:24:18,506] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] 阅读时间预测 initialized. +[2025-10-09 20:24:18,507] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: reading_time +[2025-10-09 20:24:18,507] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: reading_time +[2025-10-09 20:24:18,524] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] 外部链接处理器 initialized. +[2025-10-09 20:24:18,524] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] 外部链接处理器 initialized. +[2025-10-09 20:24:18,525] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: external_links +[2025-10-09 20:24:18,525] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: external_links +[2025-10-09 20:24:18,529] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] 文章浏览次数统计 initialized. +[2025-10-09 20:24:18,529] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] 文章浏览次数统计 initialized. +[2025-10-09 20:24:18,529] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: view_count +[2025-10-09 20:24:18,529] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: view_count +[2025-10-09 20:24:18,531] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] SEO 优化器 initialized. +[2025-10-09 20:24:18,531] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] SEO 优化器 initialized. +[2025-10-09 20:24:18,540] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: seo_optimizer +[2025-10-09 20:24:18,540] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: seo_optimizer +[2025-10-09 20:24:28,813] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] 文章结尾版权声明 initialized. +[2025-10-09 20:24:28,813] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] 文章结尾版权声明 initialized. +[2025-10-09 20:24:28,814] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: article_copyright +[2025-10-09 20:24:28,814] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: article_copyright +[2025-10-09 20:24:28,816] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] 阅读时间预测 initialized. +[2025-10-09 20:24:28,816] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] 阅读时间预测 initialized. +[2025-10-09 20:24:28,816] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: reading_time +[2025-10-09 20:24:28,816] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: reading_time +[2025-10-09 20:24:28,817] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] 外部链接处理器 initialized. +[2025-10-09 20:24:28,817] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] 外部链接处理器 initialized. +[2025-10-09 20:24:28,818] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: external_links +[2025-10-09 20:24:28,818] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: external_links +[2025-10-09 20:24:28,819] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] 文章浏览次数统计 initialized. +[2025-10-09 20:24:28,819] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] 文章浏览次数统计 initialized. +[2025-10-09 20:24:28,819] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: view_count +[2025-10-09 20:24:28,819] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: view_count +[2025-10-09 20:24:28,821] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] SEO 优化器 initialized. +[2025-10-09 20:24:28,821] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] SEO 优化器 initialized. +[2025-10-09 20:24:28,822] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: seo_optimizer +[2025-10-09 20:24:28,822] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: seo_optimizer +[2025-10-09 20:24:56,786] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] 文章结尾版权声明 initialized. +[2025-10-09 20:24:56,786] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] 文章结尾版权声明 initialized. +[2025-10-09 20:24:56,789] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: article_copyright +[2025-10-09 20:24:56,789] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: article_copyright +[2025-10-09 20:24:56,792] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] 阅读时间预测 initialized. +[2025-10-09 20:24:56,792] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] 阅读时间预测 initialized. +[2025-10-09 20:24:56,792] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: reading_time +[2025-10-09 20:24:56,792] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: reading_time +[2025-10-09 20:24:56,794] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] 外部链接处理器 initialized. +[2025-10-09 20:24:56,794] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] 外部链接处理器 initialized. +[2025-10-09 20:24:56,795] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: external_links +[2025-10-09 20:24:56,795] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: external_links +[2025-10-09 20:24:56,797] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] 文章浏览次数统计 initialized. +[2025-10-09 20:24:56,797] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] 文章浏览次数统计 initialized. +[2025-10-09 20:24:56,797] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: view_count +[2025-10-09 20:24:56,797] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: view_count +[2025-10-09 20:24:56,799] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] SEO 优化器 initialized. +[2025-10-09 20:24:56,799] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] SEO 优化器 initialized. +[2025-10-09 20:24:56,800] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: seo_optimizer +[2025-10-09 20:24:56,800] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: seo_optimizer +[2025-10-09 20:25:31,303] INFO [djangoblog.spider_notify.baidu_notify:15 spider_notify] {"remain":10,"success":0,"not_same_site":["https://example.com/author/lxl.html"]} +[2025-10-09 20:25:31,303] INFO [djangoblog.spider_notify.baidu_notify:15 spider_notify] {"remain":10,"success":0,"not_same_site":["https://example.com/author/lxl.html"]} +[2025-10-09 20:25:33,319] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] 文章结尾版权声明 initialized. +[2025-10-09 20:25:33,319] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] 文章结尾版权声明 initialized. +[2025-10-09 20:25:33,320] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: article_copyright +[2025-10-09 20:25:33,320] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: article_copyright +[2025-10-09 20:25:33,322] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] 阅读时间预测 initialized. +[2025-10-09 20:25:33,322] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] 阅读时间预测 initialized. +[2025-10-09 20:25:33,322] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: reading_time +[2025-10-09 20:25:33,322] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: reading_time +[2025-10-09 20:25:33,324] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] 外部链接处理器 initialized. +[2025-10-09 20:25:33,324] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] 外部链接处理器 initialized. +[2025-10-09 20:25:33,325] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: external_links +[2025-10-09 20:25:33,325] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: external_links +[2025-10-09 20:25:33,328] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] 文章浏览次数统计 initialized. +[2025-10-09 20:25:33,328] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] 文章浏览次数统计 initialized. +[2025-10-09 20:25:33,329] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: view_count +[2025-10-09 20:25:33,329] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: view_count +[2025-10-09 20:25:33,332] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] SEO 优化器 initialized. +[2025-10-09 20:25:33,332] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] SEO 优化器 initialized. +[2025-10-09 20:25:33,334] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: seo_optimizer +[2025-10-09 20:25:33,334] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: seo_optimizer +[2025-10-09 20:25:37,953] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] 文章结尾版权声明 initialized. +[2025-10-09 20:25:37,953] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] 文章结尾版权声明 initialized. +[2025-10-09 20:25:37,954] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: article_copyright +[2025-10-09 20:25:37,954] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: article_copyright +[2025-10-09 20:25:37,957] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] 阅读时间预测 initialized. +[2025-10-09 20:25:37,957] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] 阅读时间预测 initialized. +[2025-10-09 20:25:37,957] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: reading_time +[2025-10-09 20:25:37,957] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: reading_time +[2025-10-09 20:25:37,959] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] 外部链接处理器 initialized. +[2025-10-09 20:25:37,959] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] 外部链接处理器 initialized. +[2025-10-09 20:25:37,960] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: external_links +[2025-10-09 20:25:37,960] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: external_links +[2025-10-09 20:25:37,962] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] 文章浏览次数统计 initialized. +[2025-10-09 20:25:37,962] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] 文章浏览次数统计 initialized. +[2025-10-09 20:25:37,963] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: view_count +[2025-10-09 20:25:37,963] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: view_count +[2025-10-09 20:25:37,965] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] SEO 优化器 initialized. +[2025-10-09 20:25:37,965] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] SEO 优化器 initialized. +[2025-10-09 20:25:37,965] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: seo_optimizer +[2025-10-09 20:25:37,965] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: seo_optimizer +[2025-10-09 20:26:20,044] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] 文章结尾版权声明 initialized. +[2025-10-09 20:26:20,044] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] 文章结尾版权声明 initialized. +[2025-10-09 20:26:20,045] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: article_copyright +[2025-10-09 20:26:20,045] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: article_copyright +[2025-10-09 20:26:20,047] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] 阅读时间预测 initialized. +[2025-10-09 20:26:20,047] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] 阅读时间预测 initialized. +[2025-10-09 20:26:20,048] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: reading_time +[2025-10-09 20:26:20,048] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: reading_time +[2025-10-09 20:26:20,050] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] 外部链接处理器 initialized. +[2025-10-09 20:26:20,050] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] 外部链接处理器 initialized. +[2025-10-09 20:26:20,051] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: external_links +[2025-10-09 20:26:20,051] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: external_links +[2025-10-09 20:26:20,053] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] 文章浏览次数统计 initialized. +[2025-10-09 20:26:20,053] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] 文章浏览次数统计 initialized. +[2025-10-09 20:26:20,054] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: view_count +[2025-10-09 20:26:20,054] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: view_count +[2025-10-09 20:26:20,056] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] SEO 优化器 initialized. +[2025-10-09 20:26:20,056] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] SEO 优化器 initialized. +[2025-10-09 20:26:20,056] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: seo_optimizer +[2025-10-09 20:26:20,056] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: seo_optimizer +[2025-10-09 20:26:21,901] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] 文章结尾版权声明 initialized. +[2025-10-09 20:26:21,901] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] 文章结尾版权声明 initialized. +[2025-10-09 20:26:21,902] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: article_copyright +[2025-10-09 20:26:21,902] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: article_copyright +[2025-10-09 20:26:21,904] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] 阅读时间预测 initialized. +[2025-10-09 20:26:21,904] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] 阅读时间预测 initialized. +[2025-10-09 20:26:21,904] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: reading_time +[2025-10-09 20:26:21,904] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: reading_time +[2025-10-09 20:26:21,907] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] 外部链接处理器 initialized. +[2025-10-09 20:26:21,907] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] 外部链接处理器 initialized. +[2025-10-09 20:26:21,908] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: external_links +[2025-10-09 20:26:21,908] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: external_links +[2025-10-09 20:26:21,910] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] 文章浏览次数统计 initialized. +[2025-10-09 20:26:21,910] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] 文章浏览次数统计 initialized. +[2025-10-09 20:26:21,911] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: view_count +[2025-10-09 20:26:21,911] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: view_count +[2025-10-09 20:26:21,913] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] SEO 优化器 initialized. +[2025-10-09 20:26:21,913] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] SEO 优化器 initialized. +[2025-10-09 20:26:21,913] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: seo_optimizer +[2025-10-09 20:26:21,913] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: seo_optimizer +[2025-10-09 20:26:21,958] INFO [django.utils.autoreload.run_with_reloader:667 autoreload] Watching for file changes with StatReloader +[2025-10-09 20:26:26,711] INFO [blog.views.get_queryset_from_cache:75 views] set view cache.key:index_1 +[2025-10-09 20:26:26,731] INFO [blog.context_processors.seo_processor:17 context_processors] set processor cache. +[2025-10-09 20:26:26,745] INFO [djangoblog.utils.get_blog_setting:171 utils] set cache get_blog_setting +[2025-10-09 20:26:26,745] INFO [djangoblog.utils.get_blog_setting:171 utils] set cache get_blog_setting +[2025-10-09 20:26:27,351] INFO [blog.templatetags.blog_tags.load_sidebar:145 blog_tags] load sidebar +[2025-10-09 20:26:27,451] INFO [blog.templatetags.blog_tags.load_sidebar:189 blog_tags] set sidebar cache.key:sidebari +[2025-10-09 20:26:45,038] INFO [djangoblog.spider_notify.baidu_notify:15 spider_notify] {"remain":10,"success":0,"not_same_site":["https://example.com/author/lxl.html"]} +[2025-10-09 20:26:45,038] INFO [djangoblog.spider_notify.baidu_notify:15 spider_notify] {"remain":10,"success":0,"not_same_site":["https://example.com/author/lxl.html"]} +[2025-10-09 20:26:45,043] INFO [djangoblog.blog_signals.user_auth_callback:120 blog_signals] 123@example.com +[2025-10-09 20:26:45,043] INFO [djangoblog.blog_signals.user_auth_callback:120 blog_signals] 123@example.com +[2025-10-09 20:26:45,044] INFO [djangoblog.utils.delete_sidebar_cache:208 utils] delete sidebar key:sidebari +[2025-10-09 20:26:45,044] INFO [djangoblog.utils.delete_sidebar_cache:208 utils] delete sidebar key:sidebari +[2025-10-09 20:26:45,045] INFO [djangoblog.utils.delete_sidebar_cache:208 utils] delete sidebar key:sidebarl +[2025-10-09 20:26:45,045] INFO [djangoblog.utils.delete_sidebar_cache:208 utils] delete sidebar key:sidebarl +[2025-10-09 20:26:45,045] INFO [djangoblog.utils.delete_sidebar_cache:208 utils] delete sidebar key:sidebarp +[2025-10-09 20:26:45,045] INFO [djangoblog.utils.delete_sidebar_cache:208 utils] delete sidebar key:sidebarp +[2025-10-09 20:26:45,046] INFO [djangoblog.utils.delete_sidebar_cache:208 utils] delete sidebar key:sidebara +[2025-10-09 20:26:45,046] INFO [djangoblog.utils.delete_sidebar_cache:208 utils] delete sidebar key:sidebara +[2025-10-09 20:26:45,055] INFO [djangoblog.utils.delete_sidebar_cache:208 utils] delete sidebar key:sidebars +[2025-10-09 20:26:45,055] INFO [djangoblog.utils.delete_sidebar_cache:208 utils] delete sidebar key:sidebars +[2025-10-09 20:26:45,135] INFO [blog.context_processors.seo_processor:17 context_processors] set processor cache. +[2025-10-09 20:26:45,143] INFO [djangoblog.utils.get_blog_setting:171 utils] set cache get_blog_setting +[2025-10-09 20:26:45,143] INFO [djangoblog.utils.get_blog_setting:171 utils] set cache get_blog_setting +[2025-10-09 20:29:04,998] INFO [blog.views.get_queryset_from_cache:75 views] set view cache.key:category_list_我是父类目_1 +[2025-10-09 20:29:05,280] INFO [blog.templatetags.blog_tags.load_sidebar:145 blog_tags] load sidebar +[2025-10-09 20:29:05,354] INFO [blog.templatetags.blog_tags.load_sidebar:189 blog_tags] set sidebar cache.key:sidebarl +[2025-10-09 20:29:06,310] INFO [blog.views.get_queryset_from_cache:75 views] set view cache.key:category_list_子类目_1 +[2025-10-09 20:29:08,369] INFO [blog.views.get_queryset_from_cache:75 views] set view cache.key:archives +[2025-10-09 20:29:08,396] INFO [blog.templatetags.blog_tags.load_sidebar:145 blog_tags] load sidebar +[2025-10-09 20:29:08,421] INFO [blog.templatetags.blog_tags.load_sidebar:189 blog_tags] set sidebar cache.key:sidebari +[2025-10-09 20:29:12,319] INFO [blog.views.get_queryset_from_cache:75 views] set view cache.key:index_1 +[2025-10-09 20:34:02,809] INFO [djangoblog.spider_notify.baidu_notify:15 spider_notify] {"remain":10,"success":0,"not_same_site":["https://example.com/category/dian-shi-ju.html"]} +[2025-10-09 20:34:02,809] INFO [djangoblog.spider_notify.baidu_notify:15 spider_notify] {"remain":10,"success":0,"not_same_site":["https://example.com/category/dian-shi-ju.html"]} +[2025-10-09 20:34:02,862] INFO [blog.context_processors.seo_processor:17 context_processors] set processor cache. +[2025-10-09 20:34:02,870] INFO [djangoblog.utils.get_blog_setting:171 utils] set cache get_blog_setting +[2025-10-09 20:34:02,870] INFO [djangoblog.utils.get_blog_setting:171 utils] set cache get_blog_setting +[2025-10-09 20:34:13,326] INFO [blog.views.get_queryset_from_cache:75 views] set view cache.key:index_1 +[2025-10-09 20:34:13,619] INFO [blog.templatetags.blog_tags.load_sidebar:145 blog_tags] load sidebar +[2025-10-09 20:34:13,698] INFO [blog.templatetags.blog_tags.load_sidebar:189 blog_tags] set sidebar cache.key:sidebari +[2025-10-09 20:34:18,199] INFO [blog.views.get_queryset_from_cache:75 views] set view cache.key:category_list_电视剧_1 +[2025-10-09 20:34:18,461] INFO [blog.templatetags.blog_tags.load_sidebar:145 blog_tags] load sidebar +[2025-10-09 20:34:18,482] INFO [blog.templatetags.blog_tags.load_sidebar:189 blog_tags] set sidebar cache.key:sidebarl +[2025-10-09 20:34:19,716] INFO [blog.views.get_queryset_from_cache:75 views] set view cache.key:category_list_子类目_1 +[2025-10-09 20:34:21,259] INFO [blog.views.get_queryset_from_cache:70 views] get view cache.key:category_list_电视剧_1 +[2025-10-09 20:34:22,317] INFO [blog.views.get_queryset_from_cache:70 views] get view cache.key:category_list_子类目_1 +[2025-10-09 20:41:58,465] INFO [djangoblog.spider_notify.baidu_notify:15 spider_notify] {"remain":10,"success":0,"not_same_site":["https://example.com/category/ji-suan-ji.html"]} +[2025-10-09 20:41:58,465] INFO [djangoblog.spider_notify.baidu_notify:15 spider_notify] {"remain":10,"success":0,"not_same_site":["https://example.com/category/ji-suan-ji.html"]} +[2025-10-09 20:41:58,514] INFO [blog.context_processors.seo_processor:17 context_processors] set processor cache. +[2025-10-09 20:41:58,521] INFO [djangoblog.utils.get_blog_setting:171 utils] set cache get_blog_setting +[2025-10-09 20:41:58,521] INFO [djangoblog.utils.get_blog_setting:171 utils] set cache get_blog_setting +[2025-10-09 20:44:09,233] INFO [djangoblog.spider_notify.baidu_notify:15 spider_notify] {"remain":10,"success":0,"not_same_site":["https://example.com/author/lxl.html"]} +[2025-10-09 20:44:09,233] INFO [djangoblog.spider_notify.baidu_notify:15 spider_notify] {"remain":10,"success":0,"not_same_site":["https://example.com/author/lxl.html"]} +[2025-10-09 20:44:09,327] INFO [blog.context_processors.seo_processor:17 context_processors] set processor cache. +[2025-10-09 20:44:09,343] INFO [djangoblog.utils.get_blog_setting:171 utils] set cache get_blog_setting +[2025-10-09 20:44:09,343] INFO [djangoblog.utils.get_blog_setting:171 utils] set cache get_blog_setting +[2025-10-09 20:44:17,017] INFO [djangoblog.spider_notify.baidu_notify:15 spider_notify] {"remain":10,"success":0,"not_same_site":["https://example.com/author/lxl.html"]} +[2025-10-09 20:44:17,017] INFO [djangoblog.spider_notify.baidu_notify:15 spider_notify] {"remain":10,"success":0,"not_same_site":["https://example.com/author/lxl.html"]} +[2025-10-09 20:44:17,148] INFO [blog.context_processors.seo_processor:17 context_processors] set processor cache. +[2025-10-09 20:44:17,157] INFO [djangoblog.utils.get_blog_setting:171 utils] set cache get_blog_setting +[2025-10-09 20:44:17,157] INFO [djangoblog.utils.get_blog_setting:171 utils] set cache get_blog_setting +[2025-10-09 20:44:53,675] INFO [djangoblog.spider_notify.baidu_notify:15 spider_notify] {"remain":10,"success":0,"not_same_site":["https://example.com/article/2025/9/16/1.html"]} +[2025-10-09 20:44:53,675] INFO [djangoblog.spider_notify.baidu_notify:15 spider_notify] {"remain":10,"success":0,"not_same_site":["https://example.com/article/2025/9/16/1.html"]} +[2025-10-09 20:44:54,091] INFO [djangoblog.spider_notify.baidu_notify:15 spider_notify] {"remain":10,"success":0,"not_same_site":["https://example.com/article/2025/9/16/1.html"]} +[2025-10-09 20:44:54,091] INFO [djangoblog.spider_notify.baidu_notify:15 spider_notify] {"remain":10,"success":0,"not_same_site":["https://example.com/article/2025/9/16/1.html"]} +[2025-10-09 20:44:54,156] INFO [blog.context_processors.seo_processor:17 context_processors] set processor cache. +[2025-10-09 20:44:54,165] INFO [djangoblog.utils.get_blog_setting:171 utils] set cache get_blog_setting +[2025-10-09 20:44:54,165] INFO [djangoblog.utils.get_blog_setting:171 utils] set cache get_blog_setting +[2025-10-09 20:46:20,663] INFO [blog.views.get_queryset_from_cache:75 views] set view cache.key:index_1 +[2025-10-09 20:46:20,938] INFO [blog.templatetags.blog_tags.load_sidebar:145 blog_tags] load sidebar +[2025-10-09 20:46:21,021] INFO [blog.templatetags.blog_tags.load_sidebar:189 blog_tags] set sidebar cache.key:sidebari +[2025-10-09 20:46:24,716] INFO [blog.views.get_queryset_from_cache:70 views] get view cache.key:index_1 +[2025-10-09 20:46:29,907] INFO [blog.views.get_queryset_from_cache:75 views] set view cache.key:category_list_计算机_1 +[2025-10-09 20:46:30,159] INFO [blog.templatetags.blog_tags.load_sidebar:145 blog_tags] load sidebar +[2025-10-09 20:46:30,182] INFO [blog.templatetags.blog_tags.load_sidebar:189 blog_tags] set sidebar cache.key:sidebarl +[2025-10-09 20:46:37,264] INFO [blog.views.get_queryset_from_cache:75 views] set view cache.key:category_list_计算机_2 +[2025-10-09 20:46:44,026] INFO [blog.models.comment_list:151 models] set article comments:1 +[2025-10-09 20:46:44,152] INFO [blog.templatetags.blog_tags.load_sidebar:145 blog_tags] load sidebar +[2025-10-09 20:46:44,175] INFO [blog.templatetags.blog_tags.load_sidebar:189 blog_tags] set sidebar cache.key:sidebarp +[2025-10-09 20:47:00,090] INFO [blog.views.get_queryset_from_cache:75 views] set view cache.key:tag_标签_1 +[2025-10-09 20:47:24,617] INFO [djangoblog.spider_notify.baidu_notify:15 spider_notify] {"remain":10,"success":0,"not_same_site":["https://example.com/tag/jiao-xue.html"]} +[2025-10-09 20:47:24,617] INFO [djangoblog.spider_notify.baidu_notify:15 spider_notify] {"remain":10,"success":0,"not_same_site":["https://example.com/tag/jiao-xue.html"]} +[2025-10-09 20:47:24,665] INFO [blog.context_processors.seo_processor:17 context_processors] set processor cache. +[2025-10-09 20:47:24,671] INFO [djangoblog.utils.get_blog_setting:171 utils] set cache get_blog_setting +[2025-10-09 20:47:24,671] INFO [djangoblog.utils.get_blog_setting:171 utils] set cache get_blog_setting +[2025-10-09 20:48:01,610] INFO [blog.models.comment_list:151 models] set article comments:1 +[2025-10-09 20:48:01,734] INFO [blog.templatetags.blog_tags.load_sidebar:145 blog_tags] load sidebar +[2025-10-09 20:48:01,812] INFO [blog.templatetags.blog_tags.load_sidebar:189 blog_tags] set sidebar cache.key:sidebarp +[2025-10-09 20:49:15,005] INFO [djangoblog.spider_notify.baidu_notify:15 spider_notify] {"remain":10,"success":0,"not_same_site":["https://example.com/category/ren-gong-zhi-neng.html"]} +[2025-10-09 20:49:15,005] INFO [djangoblog.spider_notify.baidu_notify:15 spider_notify] {"remain":10,"success":0,"not_same_site":["https://example.com/category/ren-gong-zhi-neng.html"]} +[2025-10-09 20:49:15,051] INFO [blog.context_processors.seo_processor:17 context_processors] set processor cache. +[2025-10-09 20:49:15,058] INFO [djangoblog.utils.get_blog_setting:171 utils] set cache get_blog_setting +[2025-10-09 20:49:15,058] INFO [djangoblog.utils.get_blog_setting:171 utils] set cache get_blog_setting +[2025-10-09 20:49:36,899] INFO [djangoblog.spider_notify.baidu_notify:15 spider_notify] {"remain":10,"success":0,"not_same_site":["https://example.com/category/ji-suan-ji.html"]} +[2025-10-09 20:49:36,899] INFO [djangoblog.spider_notify.baidu_notify:15 spider_notify] {"remain":10,"success":0,"not_same_site":["https://example.com/category/ji-suan-ji.html"]} +[2025-10-09 20:49:36,943] INFO [blog.context_processors.seo_processor:17 context_processors] set processor cache. +[2025-10-09 20:49:36,950] INFO [djangoblog.utils.get_blog_setting:171 utils] set cache get_blog_setting +[2025-10-09 20:49:36,950] INFO [djangoblog.utils.get_blog_setting:171 utils] set cache get_blog_setting +[2025-10-09 20:49:40,845] INFO [blog.models.comment_list:151 models] set article comments:1 +[2025-10-09 20:49:40,987] INFO [blog.templatetags.blog_tags.load_sidebar:145 blog_tags] load sidebar +[2025-10-09 20:49:41,064] INFO [blog.templatetags.blog_tags.load_sidebar:189 blog_tags] set sidebar cache.key:sidebarp +[2025-10-09 20:49:46,143] INFO [blog.views.get_queryset_from_cache:75 views] set view cache.key:category_list_计算机_1 +[2025-10-09 20:49:46,213] INFO [blog.templatetags.blog_tags.load_sidebar:145 blog_tags] load sidebar +[2025-10-09 20:49:46,235] INFO [blog.templatetags.blog_tags.load_sidebar:189 blog_tags] set sidebar cache.key:sidebarl +[2025-10-09 20:49:47,517] INFO [blog.views.get_queryset_from_cache:75 views] set view cache.key:category_list_人工智能_1 +[2025-10-09 20:49:48,979] INFO [blog.views.get_queryset_from_cache:75 views] set view cache.key:index_1 +[2025-10-09 20:49:49,047] INFO [blog.templatetags.blog_tags.load_sidebar:145 blog_tags] load sidebar +[2025-10-09 20:49:49,067] INFO [blog.templatetags.blog_tags.load_sidebar:189 blog_tags] set sidebar cache.key:sidebari +[2025-10-09 20:50:19,451] INFO [blog.views.get_queryset_from_cache:70 views] get view cache.key:index_1 +[2025-10-09 20:50:30,234] INFO [blog.views.get_queryset_from_cache:70 views] get view cache.key:index_1 +[2025-10-09 20:50:52,569] INFO [blog.views.get_queryset_from_cache:70 views] get view cache.key:index_1 +[2025-10-09 22:48:15,547] INFO [blog.views.get_queryset_from_cache:70 views] get view cache.key:category_list_计算机_1 +[2025-10-09 22:48:16,485] INFO [blog.views.get_queryset_from_cache:70 views] get view cache.key:index_1 +[2025-10-09 22:51:24,558] INFO [blog.models.comment_list:151 models] set article comments:1 +[2025-10-09 23:01:50,681] INFO [blog.views.get_queryset_from_cache:70 views] get view cache.key:index_1 +[2025-10-09 23:04:53,595] INFO [blog.views.get_queryset_from_cache:70 views] get view cache.key:category_list_计算机_1 +[2025-10-09 23:04:55,850] INFO [blog.models.comment_list:151 models] set article comments:1 +[2025-10-09 23:06:36,040] INFO [djangoblog.spider_notify.baidu_notify:15 spider_notify] {"remain":10,"success":0,"not_same_site":["https://example.com/tag/xue-xi.html"]} +[2025-10-09 23:06:36,040] INFO [djangoblog.spider_notify.baidu_notify:15 spider_notify] {"remain":10,"success":0,"not_same_site":["https://example.com/tag/xue-xi.html"]} +[2025-10-09 23:06:36,070] INFO [blog.context_processors.seo_processor:17 context_processors] set processor cache. +[2025-10-09 23:06:36,076] INFO [djangoblog.utils.get_blog_setting:171 utils] set cache get_blog_setting +[2025-10-09 23:06:36,076] INFO [djangoblog.utils.get_blog_setting:171 utils] set cache get_blog_setting +[2025-10-09 23:07:56,321] INFO [djangoblog.spider_notify.baidu_notify:15 spider_notify] {"remain":10,"success":0,"not_same_site":["https://example.com/article/2025/10/9/20.html"]} +[2025-10-09 23:07:56,321] INFO [djangoblog.spider_notify.baidu_notify:15 spider_notify] {"remain":10,"success":0,"not_same_site":["https://example.com/article/2025/10/9/20.html"]} +[2025-10-09 23:07:56,361] INFO [blog.context_processors.seo_processor:17 context_processors] set processor cache. +[2025-10-09 23:07:56,367] INFO [djangoblog.utils.get_blog_setting:171 utils] set cache get_blog_setting +[2025-10-09 23:07:56,367] INFO [djangoblog.utils.get_blog_setting:171 utils] set cache get_blog_setting +[2025-10-09 23:08:05,846] INFO [blog.models.comment_list:151 models] set article comments:1 +[2025-10-09 23:08:05,951] INFO [blog.templatetags.blog_tags.load_sidebar:145 blog_tags] load sidebar +[2025-10-09 23:08:05,969] INFO [blog.templatetags.blog_tags.load_sidebar:189 blog_tags] set sidebar cache.key:sidebarp +[2025-10-09 23:08:08,490] INFO [blog.views.get_queryset_from_cache:75 views] set view cache.key:index_1 +[2025-10-09 23:08:08,542] INFO [blog.templatetags.blog_tags.load_sidebar:145 blog_tags] load sidebar +[2025-10-09 23:08:08,554] INFO [blog.templatetags.blog_tags.load_sidebar:189 blog_tags] set sidebar cache.key:sidebari +[2025-10-09 23:08:10,568] INFO [blog.views.get_queryset_from_cache:70 views] get view cache.key:index_1 +[2025-10-09 23:08:16,684] INFO [blog.views.get_queryset_from_cache:75 views] set view cache.key:category_list_计算机_1 +[2025-10-09 23:08:16,749] INFO [blog.templatetags.blog_tags.load_sidebar:145 blog_tags] load sidebar +[2025-10-09 23:08:16,765] INFO [blog.templatetags.blog_tags.load_sidebar:189 blog_tags] set sidebar cache.key:sidebarl +[2025-10-09 23:08:22,894] INFO [blog.views.get_queryset_from_cache:70 views] get view cache.key:category_list_计算机_1 +[2025-10-09 23:08:25,921] INFO [blog.models.comment_list:151 models] set article comments:1 +[2025-10-09 23:13:52,973] INFO [blog.views.get_queryset_from_cache:70 views] get view cache.key:index_1 +[2025-10-09 23:20:53,174] INFO [blog.views.get_queryset_from_cache:70 views] get view cache.key:index_1 +[2025-10-09 23:21:02,934] INFO [blog.views.get_queryset_from_cache:70 views] get view cache.key:index_1 +[2025-10-09 23:21:04,786] INFO [blog.views.get_queryset_from_cache:70 views] get view cache.key:index_1 diff --git a/src/DjangoBlog/logs/djangoblog.log.2025-10-12 b/src/DjangoBlog/logs/djangoblog.log.2025-10-12 new file mode 100644 index 0000000..efe0d9b --- /dev/null +++ b/src/DjangoBlog/logs/djangoblog.log.2025-10-12 @@ -0,0 +1,221 @@ +[2025-10-12 19:28:40,696] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] 文章结尾版权声明 initialized. +[2025-10-12 19:28:40,696] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] 文章结尾版权声明 initialized. +[2025-10-12 19:28:40,696] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: article_copyright +[2025-10-12 19:28:40,696] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: article_copyright +[2025-10-12 19:28:40,699] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] 阅读时间预测 initialized. +[2025-10-12 19:28:40,699] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] 阅读时间预测 initialized. +[2025-10-12 19:28:40,699] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: reading_time +[2025-10-12 19:28:40,699] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: reading_time +[2025-10-12 19:28:40,702] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] 外部链接处理器 initialized. +[2025-10-12 19:28:40,702] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] 外部链接处理器 initialized. +[2025-10-12 19:28:40,702] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: external_links +[2025-10-12 19:28:40,702] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: external_links +[2025-10-12 19:28:40,704] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] 文章浏览次数统计 initialized. +[2025-10-12 19:28:40,704] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] 文章浏览次数统计 initialized. +[2025-10-12 19:28:40,704] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: view_count +[2025-10-12 19:28:40,704] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: view_count +[2025-10-12 19:28:40,708] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] SEO 优化器 initialized. +[2025-10-12 19:28:40,708] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] SEO 优化器 initialized. +[2025-10-12 19:28:40,708] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: seo_optimizer +[2025-10-12 19:28:40,708] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: seo_optimizer +[2025-10-12 19:28:41,604] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] 文章结尾版权声明 initialized. +[2025-10-12 19:28:41,604] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] 文章结尾版权声明 initialized. +[2025-10-12 19:28:41,605] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: article_copyright +[2025-10-12 19:28:41,605] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: article_copyright +[2025-10-12 19:28:41,606] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] 阅读时间预测 initialized. +[2025-10-12 19:28:41,606] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] 阅读时间预测 initialized. +[2025-10-12 19:28:41,606] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: reading_time +[2025-10-12 19:28:41,606] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: reading_time +[2025-10-12 19:28:41,608] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] 外部链接处理器 initialized. +[2025-10-12 19:28:41,608] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] 外部链接处理器 initialized. +[2025-10-12 19:28:41,608] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: external_links +[2025-10-12 19:28:41,608] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: external_links +[2025-10-12 19:28:41,609] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] 文章浏览次数统计 initialized. +[2025-10-12 19:28:41,609] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] 文章浏览次数统计 initialized. +[2025-10-12 19:28:41,610] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: view_count +[2025-10-12 19:28:41,610] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: view_count +[2025-10-12 19:28:41,611] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] SEO 优化器 initialized. +[2025-10-12 19:28:41,611] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] SEO 优化器 initialized. +[2025-10-12 19:28:41,612] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: seo_optimizer +[2025-10-12 19:28:41,612] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: seo_optimizer +[2025-10-12 19:28:41,639] INFO [django.utils.autoreload.run_with_reloader:667 autoreload] Watching for file changes with StatReloader +[2025-10-12 19:28:46,951] INFO [blog.views.get_queryset_from_cache:75 views] set view cache.key:index_1 +[2025-10-12 19:28:46,966] INFO [blog.context_processors.seo_processor:17 context_processors] set processor cache. +[2025-10-12 19:28:46,983] INFO [djangoblog.utils.get_blog_setting:171 utils] set cache get_blog_setting +[2025-10-12 19:28:46,983] INFO [djangoblog.utils.get_blog_setting:171 utils] set cache get_blog_setting +[2025-10-12 19:28:47,720] INFO [blog.templatetags.blog_tags.load_sidebar:162 blog_tags] load sidebar +[2025-10-12 19:28:47,783] INFO [blog.templatetags.blog_tags.load_sidebar:214 blog_tags] set sidebar cache.key:sidebari +[2025-10-12 19:30:46,157] INFO [django.utils.autoreload.trigger_reload:265 autoreload] D:\software_project\src\DjangoBlog\servermanager\api\blogapi.py changed, reloading. +[2025-10-12 19:30:47,195] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] 文章结尾版权声明 initialized. +[2025-10-12 19:30:47,195] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] 文章结尾版权声明 initialized. +[2025-10-12 19:30:47,196] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: article_copyright +[2025-10-12 19:30:47,196] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: article_copyright +[2025-10-12 19:30:47,197] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] 阅读时间预测 initialized. +[2025-10-12 19:30:47,197] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] 阅读时间预测 initialized. +[2025-10-12 19:30:47,198] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: reading_time +[2025-10-12 19:30:47,198] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: reading_time +[2025-10-12 19:30:47,199] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] 外部链接处理器 initialized. +[2025-10-12 19:30:47,199] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] 外部链接处理器 initialized. +[2025-10-12 19:30:47,200] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: external_links +[2025-10-12 19:30:47,200] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: external_links +[2025-10-12 19:30:47,201] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] 文章浏览次数统计 initialized. +[2025-10-12 19:30:47,201] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] 文章浏览次数统计 initialized. +[2025-10-12 19:30:47,201] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: view_count +[2025-10-12 19:30:47,201] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: view_count +[2025-10-12 19:30:47,203] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] SEO 优化器 initialized. +[2025-10-12 19:30:47,203] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] SEO 优化器 initialized. +[2025-10-12 19:30:47,203] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: seo_optimizer +[2025-10-12 19:30:47,203] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: seo_optimizer +[2025-10-12 19:30:47,234] INFO [django.utils.autoreload.run_with_reloader:667 autoreload] Watching for file changes with StatReloader +[2025-10-12 19:41:06,263] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] 文章结尾版权声明 initialized. +[2025-10-12 19:41:06,263] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] 文章结尾版权声明 initialized. +[2025-10-12 19:41:06,264] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: article_copyright +[2025-10-12 19:41:06,264] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: article_copyright +[2025-10-12 19:41:06,265] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] 阅读时间预测 initialized. +[2025-10-12 19:41:06,265] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] 阅读时间预测 initialized. +[2025-10-12 19:41:06,265] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: reading_time +[2025-10-12 19:41:06,265] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: reading_time +[2025-10-12 19:41:06,266] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] 外部链接处理器 initialized. +[2025-10-12 19:41:06,266] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] 外部链接处理器 initialized. +[2025-10-12 19:41:06,266] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: external_links +[2025-10-12 19:41:06,266] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: external_links +[2025-10-12 19:41:06,267] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] 文章浏览次数统计 initialized. +[2025-10-12 19:41:06,267] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] 文章浏览次数统计 initialized. +[2025-10-12 19:41:06,267] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: view_count +[2025-10-12 19:41:06,267] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: view_count +[2025-10-12 19:41:06,268] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] SEO 优化器 initialized. +[2025-10-12 19:41:06,268] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] SEO 优化器 initialized. +[2025-10-12 19:41:06,268] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: seo_optimizer +[2025-10-12 19:41:06,268] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: seo_optimizer +[2025-10-12 19:41:15,956] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] 文章结尾版权声明 initialized. +[2025-10-12 19:41:15,956] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] 文章结尾版权声明 initialized. +[2025-10-12 19:41:15,956] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: article_copyright +[2025-10-12 19:41:15,956] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: article_copyright +[2025-10-12 19:41:15,957] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] 阅读时间预测 initialized. +[2025-10-12 19:41:15,957] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] 阅读时间预测 initialized. +[2025-10-12 19:41:15,957] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: reading_time +[2025-10-12 19:41:15,957] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: reading_time +[2025-10-12 19:41:15,958] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] 外部链接处理器 initialized. +[2025-10-12 19:41:15,958] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] 外部链接处理器 initialized. +[2025-10-12 19:41:15,958] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: external_links +[2025-10-12 19:41:15,958] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: external_links +[2025-10-12 19:41:15,959] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] 文章浏览次数统计 initialized. +[2025-10-12 19:41:15,959] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] 文章浏览次数统计 initialized. +[2025-10-12 19:41:15,959] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: view_count +[2025-10-12 19:41:15,959] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: view_count +[2025-10-12 19:41:15,960] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] SEO 优化器 initialized. +[2025-10-12 19:41:15,960] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] SEO 优化器 initialized. +[2025-10-12 19:41:15,960] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: seo_optimizer +[2025-10-12 19:41:15,960] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: seo_optimizer +[2025-10-12 19:41:29,188] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] 文章结尾版权声明 initialized. +[2025-10-12 19:41:29,188] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] 文章结尾版权声明 initialized. +[2025-10-12 19:41:29,188] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: article_copyright +[2025-10-12 19:41:29,188] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: article_copyright +[2025-10-12 19:41:29,190] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] 阅读时间预测 initialized. +[2025-10-12 19:41:29,190] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] 阅读时间预测 initialized. +[2025-10-12 19:41:29,190] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: reading_time +[2025-10-12 19:41:29,190] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: reading_time +[2025-10-12 19:41:29,191] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] 外部链接处理器 initialized. +[2025-10-12 19:41:29,191] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] 外部链接处理器 initialized. +[2025-10-12 19:41:29,191] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: external_links +[2025-10-12 19:41:29,191] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: external_links +[2025-10-12 19:41:29,192] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] 文章浏览次数统计 initialized. +[2025-10-12 19:41:29,192] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] 文章浏览次数统计 initialized. +[2025-10-12 19:41:29,192] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: view_count +[2025-10-12 19:41:29,192] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: view_count +[2025-10-12 19:41:29,193] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] SEO 优化器 initialized. +[2025-10-12 19:41:29,193] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] SEO 优化器 initialized. +[2025-10-12 19:41:29,193] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: seo_optimizer +[2025-10-12 19:41:29,193] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: seo_optimizer +[2025-10-12 19:41:30,115] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] 文章结尾版权声明 initialized. +[2025-10-12 19:41:30,115] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] 文章结尾版权声明 initialized. +[2025-10-12 19:41:30,115] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: article_copyright +[2025-10-12 19:41:30,115] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: article_copyright +[2025-10-12 19:41:30,116] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] 阅读时间预测 initialized. +[2025-10-12 19:41:30,116] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] 阅读时间预测 initialized. +[2025-10-12 19:41:30,116] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: reading_time +[2025-10-12 19:41:30,116] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: reading_time +[2025-10-12 19:41:30,117] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] 外部链接处理器 initialized. +[2025-10-12 19:41:30,117] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] 外部链接处理器 initialized. +[2025-10-12 19:41:30,117] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: external_links +[2025-10-12 19:41:30,117] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: external_links +[2025-10-12 19:41:30,118] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] 文章浏览次数统计 initialized. +[2025-10-12 19:41:30,118] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] 文章浏览次数统计 initialized. +[2025-10-12 19:41:30,119] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: view_count +[2025-10-12 19:41:30,119] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: view_count +[2025-10-12 19:41:30,120] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] SEO 优化器 initialized. +[2025-10-12 19:41:30,120] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] SEO 优化器 initialized. +[2025-10-12 19:41:30,120] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: seo_optimizer +[2025-10-12 19:41:30,120] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: seo_optimizer +[2025-10-12 19:41:30,144] INFO [django.utils.autoreload.run_with_reloader:667 autoreload] Watching for file changes with StatReloader +[2025-10-12 19:41:33,755] INFO [blog.views.get_queryset_from_cache:75 views] set view cache.key:index_1 +[2025-10-12 19:38:05,975] INFO [django.utils.autoreload.trigger_reload:265 autoreload] D:\software_project\src\DjangoBlog\djangoblog\settings.py changed, reloading. +[2025-10-12 19:41:51,725] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] 文章结尾版权声明 initialized. +[2025-10-12 19:41:51,725] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] 文章结尾版权声明 initialized. +[2025-10-12 19:41:51,725] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: article_copyright +[2025-10-12 19:41:51,725] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: article_copyright +[2025-10-12 19:41:51,726] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] 阅读时间预测 initialized. +[2025-10-12 19:41:51,726] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] 阅读时间预测 initialized. +[2025-10-12 19:41:51,727] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: reading_time +[2025-10-12 19:41:51,727] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: reading_time +[2025-10-12 19:41:51,728] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] 外部链接处理器 initialized. +[2025-10-12 19:41:51,728] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] 外部链接处理器 initialized. +[2025-10-12 19:41:51,728] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: external_links +[2025-10-12 19:41:51,728] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: external_links +[2025-10-12 19:41:51,729] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] 文章浏览次数统计 initialized. +[2025-10-12 19:41:51,729] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] 文章浏览次数统计 initialized. +[2025-10-12 19:41:51,729] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: view_count +[2025-10-12 19:41:51,729] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: view_count +[2025-10-12 19:41:51,731] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] SEO 优化器 initialized. +[2025-10-12 19:41:51,731] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] SEO 优化器 initialized. +[2025-10-12 19:41:51,732] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: seo_optimizer +[2025-10-12 19:41:51,732] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: seo_optimizer +[2025-10-12 19:41:51,756] INFO [django.utils.autoreload.run_with_reloader:667 autoreload] Watching for file changes with StatReloader +[2025-10-12 19:42:05,845] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] 文章结尾版权声明 initialized. +[2025-10-12 19:42:05,845] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] 文章结尾版权声明 initialized. +[2025-10-12 19:42:05,846] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: article_copyright +[2025-10-12 19:42:05,846] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: article_copyright +[2025-10-12 19:42:05,847] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] 阅读时间预测 initialized. +[2025-10-12 19:42:05,847] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] 阅读时间预测 initialized. +[2025-10-12 19:42:05,847] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: reading_time +[2025-10-12 19:42:05,847] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: reading_time +[2025-10-12 19:42:05,848] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] 外部链接处理器 initialized. +[2025-10-12 19:42:05,848] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] 外部链接处理器 initialized. +[2025-10-12 19:42:05,849] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: external_links +[2025-10-12 19:42:05,849] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: external_links +[2025-10-12 19:42:05,850] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] 文章浏览次数统计 initialized. +[2025-10-12 19:42:05,850] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] 文章浏览次数统计 initialized. +[2025-10-12 19:42:05,850] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: view_count +[2025-10-12 19:42:05,850] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: view_count +[2025-10-12 19:42:05,851] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] SEO 优化器 initialized. +[2025-10-12 19:42:05,851] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] SEO 优化器 initialized. +[2025-10-12 19:42:05,851] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: seo_optimizer +[2025-10-12 19:42:05,851] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: seo_optimizer +[2025-10-12 19:42:06,754] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] 文章结尾版权声明 initialized. +[2025-10-12 19:42:06,754] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] 文章结尾版权声明 initialized. +[2025-10-12 19:42:06,754] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: article_copyright +[2025-10-12 19:42:06,754] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: article_copyright +[2025-10-12 19:42:06,755] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] 阅读时间预测 initialized. +[2025-10-12 19:42:06,755] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] 阅读时间预测 initialized. +[2025-10-12 19:42:06,755] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: reading_time +[2025-10-12 19:42:06,755] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: reading_time +[2025-10-12 19:42:06,757] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] 外部链接处理器 initialized. +[2025-10-12 19:42:06,757] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] 外部链接处理器 initialized. +[2025-10-12 19:42:06,758] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: external_links +[2025-10-12 19:42:06,758] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: external_links +[2025-10-12 19:42:06,759] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] 文章浏览次数统计 initialized. +[2025-10-12 19:42:06,759] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] 文章浏览次数统计 initialized. +[2025-10-12 19:42:06,772] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: view_count +[2025-10-12 19:42:06,772] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: view_count +[2025-10-12 19:42:06,775] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] SEO 优化器 initialized. +[2025-10-12 19:42:06,775] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] SEO 优化器 initialized. +[2025-10-12 19:42:06,775] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: seo_optimizer +[2025-10-12 19:42:06,775] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: seo_optimizer +[2025-10-12 19:42:06,808] INFO [django.utils.autoreload.run_with_reloader:667 autoreload] Watching for file changes with StatReloader +[2025-10-12 19:42:37,150] INFO [blog.views.get_queryset_from_cache:75 views] set view cache.key:index_1 +[2025-10-12 19:42:37,158] INFO [blog.context_processors.seo_processor:17 context_processors] set processor cache. +[2025-10-12 19:42:37,164] INFO [djangoblog.utils.get_blog_setting:171 utils] set cache get_blog_setting +[2025-10-12 19:42:37,164] INFO [djangoblog.utils.get_blog_setting:171 utils] set cache get_blog_setting +[2025-10-12 19:42:37,461] INFO [blog.templatetags.blog_tags.load_sidebar:162 blog_tags] load sidebar +[2025-10-12 19:42:37,480] INFO [blog.templatetags.blog_tags.load_sidebar:214 blog_tags] set sidebar cache.key:sidebari +[2025-10-12 19:42:39,464] INFO [blog.views.get_queryset_from_cache:70 views] get view cache.key:index_1 diff --git a/src/DjangoBlog/logs/djangoblog.log.2025-10-16 b/src/DjangoBlog/logs/djangoblog.log.2025-10-16 new file mode 100644 index 0000000..f6d2a2b --- /dev/null +++ b/src/DjangoBlog/logs/djangoblog.log.2025-10-16 @@ -0,0 +1,108 @@ +[2025-10-16 09:02:03,481] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] 文章结尾版权声明 initialized. +[2025-10-16 09:02:03,481] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] 文章结尾版权声明 initialized. +[2025-10-16 09:02:03,483] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: article_copyright +[2025-10-16 09:02:03,483] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: article_copyright +[2025-10-16 09:02:03,493] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] 阅读时间预测 initialized. +[2025-10-16 09:02:03,493] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] 阅读时间预测 initialized. +[2025-10-16 09:02:03,494] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: reading_time +[2025-10-16 09:02:03,494] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: reading_time +[2025-10-16 09:02:03,503] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] 外部链接处理器 initialized. +[2025-10-16 09:02:03,503] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] 外部链接处理器 initialized. +[2025-10-16 09:02:03,507] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: external_links +[2025-10-16 09:02:03,507] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: external_links +[2025-10-16 09:02:03,515] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] 文章浏览次数统计 initialized. +[2025-10-16 09:02:03,515] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] 文章浏览次数统计 initialized. +[2025-10-16 09:02:03,515] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: view_count +[2025-10-16 09:02:03,515] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: view_count +[2025-10-16 09:02:03,528] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] SEO 优化器 initialized. +[2025-10-16 09:02:03,528] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] SEO 优化器 initialized. +[2025-10-16 09:02:03,528] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: seo_optimizer +[2025-10-16 09:02:03,528] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: seo_optimizer +[2025-10-16 09:02:05,135] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] 文章结尾版权声明 initialized. +[2025-10-16 09:02:05,135] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] 文章结尾版权声明 initialized. +[2025-10-16 09:02:05,136] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: article_copyright +[2025-10-16 09:02:05,136] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: article_copyright +[2025-10-16 09:02:05,141] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] 阅读时间预测 initialized. +[2025-10-16 09:02:05,141] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] 阅读时间预测 initialized. +[2025-10-16 09:02:05,141] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: reading_time +[2025-10-16 09:02:05,141] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: reading_time +[2025-10-16 09:02:05,146] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] 外部链接处理器 initialized. +[2025-10-16 09:02:05,146] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] 外部链接处理器 initialized. +[2025-10-16 09:02:05,147] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: external_links +[2025-10-16 09:02:05,147] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: external_links +[2025-10-16 09:02:05,150] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] 文章浏览次数统计 initialized. +[2025-10-16 09:02:05,150] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] 文章浏览次数统计 initialized. +[2025-10-16 09:02:05,151] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: view_count +[2025-10-16 09:02:05,151] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: view_count +[2025-10-16 09:02:05,154] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] SEO 优化器 initialized. +[2025-10-16 09:02:05,154] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] SEO 优化器 initialized. +[2025-10-16 09:02:05,154] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: seo_optimizer +[2025-10-16 09:02:05,154] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: seo_optimizer +[2025-10-16 09:02:05,196] INFO [django.utils.autoreload.run_with_reloader:667 autoreload] Watching for file changes with StatReloader +[2025-10-16 09:02:17,437] INFO [blog.views.get_queryset_from_cache:75 views] set view cache.key:index_1 +[2025-10-16 09:02:17,465] INFO [blog.context_processors.seo_processor:17 context_processors] set processor cache. +[2025-10-16 09:02:17,493] INFO [djangoblog.utils.get_blog_setting:171 utils] set cache get_blog_setting +[2025-10-16 09:02:17,493] INFO [djangoblog.utils.get_blog_setting:171 utils] set cache get_blog_setting +[2025-10-16 09:02:18,288] INFO [blog.templatetags.blog_tags.load_sidebar:162 blog_tags] load sidebar +[2025-10-16 09:02:18,343] INFO [blog.templatetags.blog_tags.load_sidebar:214 blog_tags] set sidebar cache.key:sidebari +[2025-10-16 09:03:19,859] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] 文章结尾版权声明 initialized. +[2025-10-16 09:03:19,859] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] 文章结尾版权声明 initialized. +[2025-10-16 09:03:19,860] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: article_copyright +[2025-10-16 09:03:19,860] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: article_copyright +[2025-10-16 09:03:19,862] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] 阅读时间预测 initialized. +[2025-10-16 09:03:19,862] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] 阅读时间预测 initialized. +[2025-10-16 09:03:19,862] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: reading_time +[2025-10-16 09:03:19,862] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: reading_time +[2025-10-16 09:03:19,865] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] 外部链接处理器 initialized. +[2025-10-16 09:03:19,865] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] 外部链接处理器 initialized. +[2025-10-16 09:03:19,866] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: external_links +[2025-10-16 09:03:19,866] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: external_links +[2025-10-16 09:03:19,868] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] 文章浏览次数统计 initialized. +[2025-10-16 09:03:19,868] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] 文章浏览次数统计 initialized. +[2025-10-16 09:03:19,868] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: view_count +[2025-10-16 09:03:19,868] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: view_count +[2025-10-16 09:03:19,870] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] SEO 优化器 initialized. +[2025-10-16 09:03:19,870] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] SEO 优化器 initialized. +[2025-10-16 09:03:19,870] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: seo_optimizer +[2025-10-16 09:03:19,870] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: seo_optimizer +[2025-10-16 09:20:25,847] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] 文章结尾版权声明 initialized. +[2025-10-16 09:20:25,847] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] 文章结尾版权声明 initialized. +[2025-10-16 09:20:25,848] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: article_copyright +[2025-10-16 09:20:25,848] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: article_copyright +[2025-10-16 09:20:25,850] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] 阅读时间预测 initialized. +[2025-10-16 09:20:25,850] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] 阅读时间预测 initialized. +[2025-10-16 09:20:25,850] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: reading_time +[2025-10-16 09:20:25,850] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: reading_time +[2025-10-16 09:20:25,852] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] 外部链接处理器 initialized. +[2025-10-16 09:20:25,852] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] 外部链接处理器 initialized. +[2025-10-16 09:20:25,853] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: external_links +[2025-10-16 09:20:25,853] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: external_links +[2025-10-16 09:20:25,855] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] 文章浏览次数统计 initialized. +[2025-10-16 09:20:25,855] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] 文章浏览次数统计 initialized. +[2025-10-16 09:20:25,855] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: view_count +[2025-10-16 09:20:25,855] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: view_count +[2025-10-16 09:20:25,857] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] SEO 优化器 initialized. +[2025-10-16 09:20:25,857] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] SEO 优化器 initialized. +[2025-10-16 09:20:25,867] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: seo_optimizer +[2025-10-16 09:20:25,867] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: seo_optimizer +[2025-10-16 09:20:27,489] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] 文章结尾版权声明 initialized. +[2025-10-16 09:20:27,489] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] 文章结尾版权声明 initialized. +[2025-10-16 09:20:27,491] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: article_copyright +[2025-10-16 09:20:27,491] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: article_copyright +[2025-10-16 09:20:27,492] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] 阅读时间预测 initialized. +[2025-10-16 09:20:27,492] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] 阅读时间预测 initialized. +[2025-10-16 09:20:27,494] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: reading_time +[2025-10-16 09:20:27,494] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: reading_time +[2025-10-16 09:20:27,498] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] 外部链接处理器 initialized. +[2025-10-16 09:20:27,498] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] 外部链接处理器 initialized. +[2025-10-16 09:20:27,498] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: external_links +[2025-10-16 09:20:27,498] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: external_links +[2025-10-16 09:20:27,503] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] 文章浏览次数统计 initialized. +[2025-10-16 09:20:27,503] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] 文章浏览次数统计 initialized. +[2025-10-16 09:20:27,503] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: view_count +[2025-10-16 09:20:27,503] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: view_count +[2025-10-16 09:20:27,506] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] SEO 优化器 initialized. +[2025-10-16 09:20:27,506] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:23 base_plugin] SEO 优化器 initialized. +[2025-10-16 09:20:27,506] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: seo_optimizer +[2025-10-16 09:20:27,506] INFO [djangoblog.plugin_manage.loader.load_plugins:17 loader] Successfully loaded plugin: seo_optimizer +[2025-10-16 09:20:27,548] INFO [django.utils.autoreload.run_with_reloader:667 autoreload] Watching for file changes with StatReloader diff --git a/src/manage.py b/src/DjangoBlog/manage.py similarity index 100% rename from src/manage.py rename to src/DjangoBlog/manage.py diff --git a/src/oauth/migrations/__init__.py b/src/DjangoBlog/oauth/__init__.py similarity index 100% rename from src/oauth/migrations/__init__.py rename to src/DjangoBlog/oauth/__init__.py diff --git a/src/DjangoBlog/oauth/__pycache__/__init__.cpython-310.pyc b/src/DjangoBlog/oauth/__pycache__/__init__.cpython-310.pyc new file mode 100644 index 0000000..c995f37 Binary files /dev/null and b/src/DjangoBlog/oauth/__pycache__/__init__.cpython-310.pyc differ diff --git a/src/DjangoBlog/oauth/__pycache__/admin.cpython-310.pyc b/src/DjangoBlog/oauth/__pycache__/admin.cpython-310.pyc new file mode 100644 index 0000000..0fcc64d Binary files /dev/null and b/src/DjangoBlog/oauth/__pycache__/admin.cpython-310.pyc differ diff --git a/src/DjangoBlog/oauth/__pycache__/apps.cpython-310.pyc b/src/DjangoBlog/oauth/__pycache__/apps.cpython-310.pyc new file mode 100644 index 0000000..23d30c5 Binary files /dev/null and b/src/DjangoBlog/oauth/__pycache__/apps.cpython-310.pyc differ diff --git a/src/DjangoBlog/oauth/__pycache__/forms.cpython-310.pyc b/src/DjangoBlog/oauth/__pycache__/forms.cpython-310.pyc new file mode 100644 index 0000000..0e55b59 Binary files /dev/null and b/src/DjangoBlog/oauth/__pycache__/forms.cpython-310.pyc differ diff --git a/src/DjangoBlog/oauth/__pycache__/models.cpython-310.pyc b/src/DjangoBlog/oauth/__pycache__/models.cpython-310.pyc new file mode 100644 index 0000000..d84de00 Binary files /dev/null and b/src/DjangoBlog/oauth/__pycache__/models.cpython-310.pyc differ diff --git a/src/DjangoBlog/oauth/__pycache__/oauthmanager.cpython-310.pyc b/src/DjangoBlog/oauth/__pycache__/oauthmanager.cpython-310.pyc new file mode 100644 index 0000000..dbd9f74 Binary files /dev/null and b/src/DjangoBlog/oauth/__pycache__/oauthmanager.cpython-310.pyc differ diff --git a/src/DjangoBlog/oauth/__pycache__/urls.cpython-310.pyc b/src/DjangoBlog/oauth/__pycache__/urls.cpython-310.pyc new file mode 100644 index 0000000..af30302 Binary files /dev/null and b/src/DjangoBlog/oauth/__pycache__/urls.cpython-310.pyc differ diff --git a/src/DjangoBlog/oauth/__pycache__/views.cpython-310.pyc b/src/DjangoBlog/oauth/__pycache__/views.cpython-310.pyc new file mode 100644 index 0000000..e9ac55d Binary files /dev/null and b/src/DjangoBlog/oauth/__pycache__/views.cpython-310.pyc differ diff --git a/src/DjangoBlog/oauth/admin.py b/src/DjangoBlog/oauth/admin.py new file mode 100644 index 0000000..57eab5f --- /dev/null +++ b/src/DjangoBlog/oauth/admin.py @@ -0,0 +1,54 @@ +import logging + +from django.contrib import admin +# Register your models here. +from django.urls import reverse +from django.utils.html import format_html + +logger = logging.getLogger(__name__) + + +class OAuthUserAdmin(admin.ModelAdmin): + search_fields = ('nickname', 'email') + list_per_page = 20 + list_display = ( + 'id', + 'nickname', + 'link_to_usermodel', + 'show_user_image', + 'type', + 'email', + ) + list_display_links = ('id', 'nickname') + list_filter = ('author', 'type',) + readonly_fields = [] + + def get_readonly_fields(self, request, obj=None): + return list(self.readonly_fields) + \ + [field.name for field in obj._meta.fields] + \ + [field.name for field in obj._meta.many_to_many] + + def has_add_permission(self, request): + return False + + def link_to_usermodel(self, obj): + if obj.author: + info = (obj.author._meta.app_label, obj.author._meta.model_name) + link = reverse('admin:%s_%s_change' % info, args=(obj.author.id,)) + return format_html( + u'%s' % + (link, obj.author.nickname if obj.author.nickname else obj.author.email)) + + def show_user_image(self, obj): + img = obj.picture + return format_html( + u'' % + (img)) + + link_to_usermodel.short_description = '用户' + show_user_image.short_description = '用户头像' + + +class OAuthConfigAdmin(admin.ModelAdmin): + list_display = ('type', 'appkey', 'appsecret', 'is_enable') + list_filter = ('type',) diff --git a/src/oauth/apps.py b/src/DjangoBlog/oauth/apps.py similarity index 100% rename from src/oauth/apps.py rename to src/DjangoBlog/oauth/apps.py diff --git a/src/oauth/forms.py b/src/DjangoBlog/oauth/forms.py similarity index 100% rename from src/oauth/forms.py rename to src/DjangoBlog/oauth/forms.py diff --git a/src/oauth/migrations/0001_initial.py b/src/DjangoBlog/oauth/migrations/0001_initial.py similarity index 100% rename from src/oauth/migrations/0001_initial.py rename to src/DjangoBlog/oauth/migrations/0001_initial.py diff --git a/src/oauth/migrations/0002_alter_oauthconfig_options_alter_oauthuser_options_and_more.py b/src/DjangoBlog/oauth/migrations/0002_alter_oauthconfig_options_alter_oauthuser_options_and_more.py similarity index 100% rename from src/oauth/migrations/0002_alter_oauthconfig_options_alter_oauthuser_options_and_more.py rename to src/DjangoBlog/oauth/migrations/0002_alter_oauthconfig_options_alter_oauthuser_options_and_more.py diff --git a/src/oauth/migrations/0003_alter_oauthuser_nickname.py b/src/DjangoBlog/oauth/migrations/0003_alter_oauthuser_nickname.py similarity index 100% rename from src/oauth/migrations/0003_alter_oauthuser_nickname.py rename to src/DjangoBlog/oauth/migrations/0003_alter_oauthuser_nickname.py diff --git a/src/owntracks/__init__.py b/src/DjangoBlog/oauth/migrations/__init__.py similarity index 100% rename from src/owntracks/__init__.py rename to src/DjangoBlog/oauth/migrations/__init__.py diff --git a/src/DjangoBlog/oauth/migrations/__pycache__/0001_initial.cpython-310.pyc b/src/DjangoBlog/oauth/migrations/__pycache__/0001_initial.cpython-310.pyc new file mode 100644 index 0000000..cd606bd Binary files /dev/null and b/src/DjangoBlog/oauth/migrations/__pycache__/0001_initial.cpython-310.pyc differ diff --git a/src/DjangoBlog/oauth/migrations/__pycache__/0002_alter_oauthconfig_options_alter_oauthuser_options_and_more.cpython-310.pyc b/src/DjangoBlog/oauth/migrations/__pycache__/0002_alter_oauthconfig_options_alter_oauthuser_options_and_more.cpython-310.pyc new file mode 100644 index 0000000..a2ae0ab Binary files /dev/null and b/src/DjangoBlog/oauth/migrations/__pycache__/0002_alter_oauthconfig_options_alter_oauthuser_options_and_more.cpython-310.pyc differ diff --git a/src/DjangoBlog/oauth/migrations/__pycache__/0003_alter_oauthuser_nickname.cpython-310.pyc b/src/DjangoBlog/oauth/migrations/__pycache__/0003_alter_oauthuser_nickname.cpython-310.pyc new file mode 100644 index 0000000..317cb0f Binary files /dev/null and b/src/DjangoBlog/oauth/migrations/__pycache__/0003_alter_oauthuser_nickname.cpython-310.pyc differ diff --git a/src/DjangoBlog/oauth/migrations/__pycache__/__init__.cpython-310.pyc b/src/DjangoBlog/oauth/migrations/__pycache__/__init__.cpython-310.pyc new file mode 100644 index 0000000..1c91eea Binary files /dev/null and b/src/DjangoBlog/oauth/migrations/__pycache__/__init__.cpython-310.pyc differ diff --git a/src/oauth/models.py b/src/DjangoBlog/oauth/models.py similarity index 100% rename from src/oauth/models.py rename to src/DjangoBlog/oauth/models.py diff --git a/src/oauth/oauthmanager.py b/src/DjangoBlog/oauth/oauthmanager.py similarity index 100% rename from src/oauth/oauthmanager.py rename to src/DjangoBlog/oauth/oauthmanager.py diff --git a/src/oauth/templatetags/__init__.py b/src/DjangoBlog/oauth/templatetags/__init__.py similarity index 100% rename from src/oauth/templatetags/__init__.py rename to src/DjangoBlog/oauth/templatetags/__init__.py diff --git a/src/DjangoBlog/oauth/templatetags/__pycache__/__init__.cpython-310.pyc b/src/DjangoBlog/oauth/templatetags/__pycache__/__init__.cpython-310.pyc new file mode 100644 index 0000000..ecc576a Binary files /dev/null and b/src/DjangoBlog/oauth/templatetags/__pycache__/__init__.cpython-310.pyc differ diff --git a/src/DjangoBlog/oauth/templatetags/__pycache__/oauth_tags.cpython-310.pyc b/src/DjangoBlog/oauth/templatetags/__pycache__/oauth_tags.cpython-310.pyc new file mode 100644 index 0000000..445ce06 Binary files /dev/null and b/src/DjangoBlog/oauth/templatetags/__pycache__/oauth_tags.cpython-310.pyc differ diff --git a/src/oauth/templatetags/oauth_tags.py b/src/DjangoBlog/oauth/templatetags/oauth_tags.py similarity index 100% rename from src/oauth/templatetags/oauth_tags.py rename to src/DjangoBlog/oauth/templatetags/oauth_tags.py diff --git a/src/oauth/tests.py b/src/DjangoBlog/oauth/tests.py similarity index 100% rename from src/oauth/tests.py rename to src/DjangoBlog/oauth/tests.py diff --git a/src/oauth/urls.py b/src/DjangoBlog/oauth/urls.py similarity index 100% rename from src/oauth/urls.py rename to src/DjangoBlog/oauth/urls.py diff --git a/src/oauth/views.py b/src/DjangoBlog/oauth/views.py similarity index 92% rename from src/oauth/views.py rename to src/DjangoBlog/oauth/views.py index a694c1e..12e3a6e 100644 --- a/src/oauth/views.py +++ b/src/DjangoBlog/oauth/views.py @@ -25,7 +25,7 @@ from .oauthmanager import get_manager_by_type, OAuthAccessTokenException logger = logging.getLogger(__name__) -# 获取重定向的 URL + def get_redirecturl(request): nexturl = request.GET.get('next_url', None) if not nexturl or nexturl == '/login/' or nexturl == '/login': @@ -34,13 +34,12 @@ def get_redirecturl(request): p = urlparse(nexturl) if p.netloc: site = get_current_site().domain - # 验证 URL 是否属于当前站点 if not p.netloc.replace('www.', '') == site.replace('www.', ''): logger.info('非法url:' + nexturl) return "/" return nexturl -# OAuth 登录视图 + def oauthlogin(request): type = request.GET.get('type', None) if not type: @@ -49,11 +48,10 @@ def oauthlogin(request): if not manager: return HttpResponseRedirect('/') nexturl = get_redirecturl(request) - # 获取授权 URL 并重定向 authorizeurl = manager.get_authorization_url(nexturl) return HttpResponseRedirect(authorizeurl) -# OAuth 授权回调处理 + def authorize(request): type = request.GET.get('type', None) if not type: @@ -63,7 +61,6 @@ def authorize(request): return HttpResponseRedirect('/') code = request.GET.get('code', None) try: - # 使用授权码获取访问令牌 rsp = manager.get_access_token_by_code(code) except OAuthAccessTokenException as e: logger.warning("OAuthAccessTokenException:" + str(e)) @@ -76,11 +73,9 @@ def authorize(request): return HttpResponseRedirect(manager.get_authorization_url(nexturl)) user = manager.get_oauth_userinfo() if user: - # 设置默认昵称 if not user.nickname or not user.nickname.strip(): user.nickname = "djangoblog" + timezone.now().strftime('%y%m%d%I%M%S') try: - # 检查是否存在相同的 OAuth 用户 temp = OAuthUser.objects.get(type=type, openid=user.openid) temp.picture = user.picture temp.metadata = user.metadata @@ -88,19 +83,17 @@ def authorize(request): user = temp except ObjectDoesNotExist: pass - # 特殊处理 Facebook 的 token + # facebook的token过长 if type == 'facebook': user.token = '' if user.email: with transaction.atomic(): 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]: @@ -116,13 +109,11 @@ def authorize(request): user.author = author user.save() - # 发送登录信号 oauth_user_login_signal.send( sender=authorize.__class__, id=user.id) login(request, author) return HttpResponseRedirect(nexturl) else: - # 如果没有邮箱,跳转到邮箱绑定页面 user.save() url = reverse('oauth:require_email', kwargs={ 'oauthid': user.id @@ -132,11 +123,10 @@ def authorize(request): else: return HttpResponseRedirect(nexturl) -# 邮箱确认视图 + def emailconfirm(request, id, sign): if not sign: return HttpResponseForbidden() - # 验证签名 if not get_sha256(settings.SECRET_KEY + str(id) + settings.SECRET_KEY).upper() == sign.upper(): @@ -146,7 +136,6 @@ def emailconfirm(request, id, sign): 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]: @@ -156,13 +145,11 @@ def emailconfirm(request, id, sign): author.save() oauthuser.author = author oauthuser.save() - # 发送登录信号 oauth_user_login_signal.send( sender=emailconfirm.__class__, id=oauthuser.id) login(request, author) - # 发送绑定成功邮件 site = 'http://' + get_current_site().domain content = _('''

Congratulations, you have successfully bound your email address. You can use @@ -182,7 +169,7 @@ def emailconfirm(request, id, sign): url = url + '?type=success' return HttpResponseRedirect(url) -# 绑定邮箱的表单视图 + class RequireEmailView(FormView): form_class = RequireEmailForm template_name = 'oauth/require_email.html' @@ -216,7 +203,6 @@ class RequireEmailView(FormView): oauthuser = get_object_or_404(OAuthUser, pk=oauthid) oauthuser.email = email oauthuser.save() - # 生成签名并发送绑定邮件 sign = get_sha256(settings.SECRET_KEY + str(oauthuser.id) + settings.SECRET_KEY) site = get_current_site().domain @@ -246,7 +232,7 @@ class RequireEmailView(FormView): url = url + '?type=email' return HttpResponseRedirect(url) -# 绑定成功页面 + def bindsuccess(request, oauthid): type = request.GET.get('type', None) oauthuser = get_object_or_404(OAuthUser, pk=oauthid) diff --git a/src/owntracks/migrations/__init__.py b/src/DjangoBlog/owntracks/__init__.py similarity index 100% rename from src/owntracks/migrations/__init__.py rename to src/DjangoBlog/owntracks/__init__.py diff --git a/src/DjangoBlog/owntracks/__pycache__/__init__.cpython-310.pyc b/src/DjangoBlog/owntracks/__pycache__/__init__.cpython-310.pyc new file mode 100644 index 0000000..ac9b286 Binary files /dev/null and b/src/DjangoBlog/owntracks/__pycache__/__init__.cpython-310.pyc differ diff --git a/src/DjangoBlog/owntracks/__pycache__/admin.cpython-310.pyc b/src/DjangoBlog/owntracks/__pycache__/admin.cpython-310.pyc new file mode 100644 index 0000000..e0568bb Binary files /dev/null and b/src/DjangoBlog/owntracks/__pycache__/admin.cpython-310.pyc differ diff --git a/src/DjangoBlog/owntracks/__pycache__/apps.cpython-310.pyc b/src/DjangoBlog/owntracks/__pycache__/apps.cpython-310.pyc new file mode 100644 index 0000000..cd9615a Binary files /dev/null and b/src/DjangoBlog/owntracks/__pycache__/apps.cpython-310.pyc differ diff --git a/src/DjangoBlog/owntracks/__pycache__/models.cpython-310.pyc b/src/DjangoBlog/owntracks/__pycache__/models.cpython-310.pyc new file mode 100644 index 0000000..2439f96 Binary files /dev/null and b/src/DjangoBlog/owntracks/__pycache__/models.cpython-310.pyc differ diff --git a/src/DjangoBlog/owntracks/__pycache__/urls.cpython-310.pyc b/src/DjangoBlog/owntracks/__pycache__/urls.cpython-310.pyc new file mode 100644 index 0000000..1c1ca7e Binary files /dev/null and b/src/DjangoBlog/owntracks/__pycache__/urls.cpython-310.pyc differ diff --git a/src/DjangoBlog/owntracks/__pycache__/views.cpython-310.pyc b/src/DjangoBlog/owntracks/__pycache__/views.cpython-310.pyc new file mode 100644 index 0000000..d7f66a9 Binary files /dev/null and b/src/DjangoBlog/owntracks/__pycache__/views.cpython-310.pyc differ diff --git a/src/owntracks/admin.py b/src/DjangoBlog/owntracks/admin.py similarity index 100% rename from src/owntracks/admin.py rename to src/DjangoBlog/owntracks/admin.py diff --git a/src/owntracks/apps.py b/src/DjangoBlog/owntracks/apps.py similarity index 100% rename from src/owntracks/apps.py rename to src/DjangoBlog/owntracks/apps.py diff --git a/src/owntracks/migrations/0001_initial.py b/src/DjangoBlog/owntracks/migrations/0001_initial.py similarity index 100% rename from src/owntracks/migrations/0001_initial.py rename to src/DjangoBlog/owntracks/migrations/0001_initial.py diff --git a/src/owntracks/migrations/0002_alter_owntracklog_options_and_more.py b/src/DjangoBlog/owntracks/migrations/0002_alter_owntracklog_options_and_more.py similarity index 100% rename from src/owntracks/migrations/0002_alter_owntracklog_options_and_more.py rename to src/DjangoBlog/owntracks/migrations/0002_alter_owntracklog_options_and_more.py diff --git a/src/servermanager/__init__.py b/src/DjangoBlog/owntracks/migrations/__init__.py similarity index 100% rename from src/servermanager/__init__.py rename to src/DjangoBlog/owntracks/migrations/__init__.py diff --git a/src/DjangoBlog/owntracks/migrations/__pycache__/0001_initial.cpython-310.pyc b/src/DjangoBlog/owntracks/migrations/__pycache__/0001_initial.cpython-310.pyc new file mode 100644 index 0000000..7d7ddb2 Binary files /dev/null and b/src/DjangoBlog/owntracks/migrations/__pycache__/0001_initial.cpython-310.pyc differ diff --git a/src/DjangoBlog/owntracks/migrations/__pycache__/0002_alter_owntracklog_options_and_more.cpython-310.pyc b/src/DjangoBlog/owntracks/migrations/__pycache__/0002_alter_owntracklog_options_and_more.cpython-310.pyc new file mode 100644 index 0000000..e6a9acb Binary files /dev/null and b/src/DjangoBlog/owntracks/migrations/__pycache__/0002_alter_owntracklog_options_and_more.cpython-310.pyc differ diff --git a/src/DjangoBlog/owntracks/migrations/__pycache__/__init__.cpython-310.pyc b/src/DjangoBlog/owntracks/migrations/__pycache__/__init__.cpython-310.pyc new file mode 100644 index 0000000..99d946a Binary files /dev/null and b/src/DjangoBlog/owntracks/migrations/__pycache__/__init__.cpython-310.pyc differ diff --git a/src/owntracks/models.py b/src/DjangoBlog/owntracks/models.py similarity index 100% rename from src/owntracks/models.py rename to src/DjangoBlog/owntracks/models.py diff --git a/src/owntracks/tests.py b/src/DjangoBlog/owntracks/tests.py similarity index 100% rename from src/owntracks/tests.py rename to src/DjangoBlog/owntracks/tests.py diff --git a/src/owntracks/urls.py b/src/DjangoBlog/owntracks/urls.py similarity index 100% rename from src/owntracks/urls.py rename to src/DjangoBlog/owntracks/urls.py diff --git a/src/owntracks/views.py b/src/DjangoBlog/owntracks/views.py similarity index 100% rename from src/owntracks/views.py rename to src/DjangoBlog/owntracks/views.py diff --git a/src/plugins/__init__.py b/src/DjangoBlog/plugins/__init__.py similarity index 100% rename from src/plugins/__init__.py rename to src/DjangoBlog/plugins/__init__.py diff --git a/src/DjangoBlog/plugins/__pycache__/__init__.cpython-310.pyc b/src/DjangoBlog/plugins/__pycache__/__init__.cpython-310.pyc new file mode 100644 index 0000000..76f112e Binary files /dev/null and b/src/DjangoBlog/plugins/__pycache__/__init__.cpython-310.pyc differ diff --git a/src/plugins/article_copyright/__init__.py b/src/DjangoBlog/plugins/article_copyright/__init__.py similarity index 100% rename from src/plugins/article_copyright/__init__.py rename to src/DjangoBlog/plugins/article_copyright/__init__.py diff --git a/src/DjangoBlog/plugins/article_copyright/__pycache__/__init__.cpython-310.pyc b/src/DjangoBlog/plugins/article_copyright/__pycache__/__init__.cpython-310.pyc new file mode 100644 index 0000000..554c46a Binary files /dev/null and b/src/DjangoBlog/plugins/article_copyright/__pycache__/__init__.cpython-310.pyc differ diff --git a/src/DjangoBlog/plugins/article_copyright/__pycache__/plugin.cpython-310.pyc b/src/DjangoBlog/plugins/article_copyright/__pycache__/plugin.cpython-310.pyc new file mode 100644 index 0000000..7f973c6 Binary files /dev/null and b/src/DjangoBlog/plugins/article_copyright/__pycache__/plugin.cpython-310.pyc differ diff --git a/src/plugins/article_copyright/plugin.py b/src/DjangoBlog/plugins/article_copyright/plugin.py similarity index 88% rename from src/plugins/article_copyright/plugin.py rename to src/DjangoBlog/plugins/article_copyright/plugin.py index 5dba3b3..317fed2 100644 --- a/src/plugins/article_copyright/plugin.py +++ b/src/DjangoBlog/plugins/article_copyright/plugin.py @@ -22,11 +22,6 @@ class ArticleCopyrightPlugin(BasePlugin): article = kwargs.get('article') if not article: return content - - # 如果是摘要模式(首页),不添加版权声明 - is_summary = kwargs.get('is_summary', False) - if is_summary: - return content copyright_info = f"\n


本文由 {article.author.username} 原创,转载请注明出处。

" return content + copyright_info diff --git a/src/plugins/external_links/__init__.py b/src/DjangoBlog/plugins/external_links/__init__.py similarity index 100% rename from src/plugins/external_links/__init__.py rename to src/DjangoBlog/plugins/external_links/__init__.py diff --git a/src/DjangoBlog/plugins/external_links/__pycache__/__init__.cpython-310.pyc b/src/DjangoBlog/plugins/external_links/__pycache__/__init__.cpython-310.pyc new file mode 100644 index 0000000..29e23ea Binary files /dev/null and b/src/DjangoBlog/plugins/external_links/__pycache__/__init__.cpython-310.pyc differ diff --git a/src/DjangoBlog/plugins/external_links/__pycache__/plugin.cpython-310.pyc b/src/DjangoBlog/plugins/external_links/__pycache__/plugin.cpython-310.pyc new file mode 100644 index 0000000..20ae765 Binary files /dev/null and b/src/DjangoBlog/plugins/external_links/__pycache__/plugin.cpython-310.pyc differ diff --git a/src/plugins/external_links/plugin.py b/src/DjangoBlog/plugins/external_links/plugin.py similarity index 100% rename from src/plugins/external_links/plugin.py rename to src/DjangoBlog/plugins/external_links/plugin.py diff --git a/src/plugins/reading_time/__init__.py b/src/DjangoBlog/plugins/reading_time/__init__.py similarity index 100% rename from src/plugins/reading_time/__init__.py rename to src/DjangoBlog/plugins/reading_time/__init__.py diff --git a/src/DjangoBlog/plugins/reading_time/__pycache__/__init__.cpython-310.pyc b/src/DjangoBlog/plugins/reading_time/__pycache__/__init__.cpython-310.pyc new file mode 100644 index 0000000..09b7cd8 Binary files /dev/null and b/src/DjangoBlog/plugins/reading_time/__pycache__/__init__.cpython-310.pyc differ diff --git a/src/DjangoBlog/plugins/reading_time/__pycache__/plugin.cpython-310.pyc b/src/DjangoBlog/plugins/reading_time/__pycache__/plugin.cpython-310.pyc new file mode 100644 index 0000000..fd25160 Binary files /dev/null and b/src/DjangoBlog/plugins/reading_time/__pycache__/plugin.cpython-310.pyc differ diff --git a/src/plugins/reading_time/plugin.py b/src/DjangoBlog/plugins/reading_time/plugin.py similarity index 78% rename from src/plugins/reading_time/plugin.py rename to src/DjangoBlog/plugins/reading_time/plugin.py index 4b929d8..35f9db1 100644 --- a/src/plugins/reading_time/plugin.py +++ b/src/DjangoBlog/plugins/reading_time/plugin.py @@ -17,15 +17,7 @@ class ReadingTimePlugin(BasePlugin): def add_reading_time(self, content, *args, **kwargs): """ 计算阅读时间并添加到内容开头。 - 只在文章详情页显示,首页(文章列表页)不显示。 """ - # 检查是否为摘要模式(首页/文章列表页) - # 通过kwargs中的is_summary参数判断 - is_summary = kwargs.get('is_summary', False) - if is_summary: - # 如果是摘要模式(首页),直接返回原内容,不添加阅读时间 - return content - # 移除HTML标签和空白字符,以获得纯文本 clean_content = re.sub(r'<[^>]*>', '', content) clean_content = clean_content.strip() diff --git a/src/plugins/seo_optimizer/__init__.py b/src/DjangoBlog/plugins/seo_optimizer/__init__.py similarity index 100% rename from src/plugins/seo_optimizer/__init__.py rename to src/DjangoBlog/plugins/seo_optimizer/__init__.py diff --git a/src/DjangoBlog/plugins/seo_optimizer/__pycache__/__init__.cpython-310.pyc b/src/DjangoBlog/plugins/seo_optimizer/__pycache__/__init__.cpython-310.pyc new file mode 100644 index 0000000..d612bb9 Binary files /dev/null and b/src/DjangoBlog/plugins/seo_optimizer/__pycache__/__init__.cpython-310.pyc differ diff --git a/src/DjangoBlog/plugins/seo_optimizer/__pycache__/plugin.cpython-310.pyc b/src/DjangoBlog/plugins/seo_optimizer/__pycache__/plugin.cpython-310.pyc new file mode 100644 index 0000000..0d3e66b Binary files /dev/null and b/src/DjangoBlog/plugins/seo_optimizer/__pycache__/plugin.cpython-310.pyc differ diff --git a/src/plugins/seo_optimizer/plugin.py b/src/DjangoBlog/plugins/seo_optimizer/plugin.py similarity index 96% rename from src/plugins/seo_optimizer/plugin.py rename to src/DjangoBlog/plugins/seo_optimizer/plugin.py index de12c15..b5b19a3 100644 --- a/src/plugins/seo_optimizer/plugin.py +++ b/src/DjangoBlog/plugins/seo_optimizer/plugin.py @@ -97,8 +97,6 @@ class SeoOptimizerPlugin(BasePlugin): structured_data = { "@context": "https://schema.org", "@type": "WebSite", - "name": blog_setting.site_name, - "description": blog_setting.site_description, "url": request.build_absolute_uri('/'), "potentialAction": { "@type": "SearchAction", @@ -133,15 +131,12 @@ class SeoOptimizerPlugin(BasePlugin): json_ld_script = f'' - seo_html = f""" + return f""" {seo_data.get("title", "")} {seo_data.get("meta_tags", "")} {json_ld_script} """ - - # 将SEO内容追加到现有的metas内容上 - return metas + seo_html plugin = SeoOptimizerPlugin() diff --git a/src/plugins/view_count/__init__.py b/src/DjangoBlog/plugins/view_count/__init__.py similarity index 100% rename from src/plugins/view_count/__init__.py rename to src/DjangoBlog/plugins/view_count/__init__.py diff --git a/src/DjangoBlog/plugins/view_count/__pycache__/__init__.cpython-310.pyc b/src/DjangoBlog/plugins/view_count/__pycache__/__init__.cpython-310.pyc new file mode 100644 index 0000000..4e46bd4 Binary files /dev/null and b/src/DjangoBlog/plugins/view_count/__pycache__/__init__.cpython-310.pyc differ diff --git a/src/DjangoBlog/plugins/view_count/__pycache__/plugin.cpython-310.pyc b/src/DjangoBlog/plugins/view_count/__pycache__/plugin.cpython-310.pyc new file mode 100644 index 0000000..858d7ca Binary files /dev/null and b/src/DjangoBlog/plugins/view_count/__pycache__/plugin.cpython-310.pyc differ diff --git a/src/plugins/view_count/plugin.py b/src/DjangoBlog/plugins/view_count/plugin.py similarity index 100% rename from src/plugins/view_count/plugin.py rename to src/DjangoBlog/plugins/view_count/plugin.py diff --git a/src/requirements.txt b/src/DjangoBlog/requirements.txt similarity index 62% rename from src/requirements.txt rename to src/DjangoBlog/requirements.txt index e5878ab..9dc5c93 100644 Binary files a/src/requirements.txt and b/src/DjangoBlog/requirements.txt differ diff --git a/src/DjangoBlog/servermanager/MemcacheStorage.py b/src/DjangoBlog/servermanager/MemcacheStorage.py new file mode 100644 index 0000000..38a7990 --- /dev/null +++ b/src/DjangoBlog/servermanager/MemcacheStorage.py @@ -0,0 +1,32 @@ +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) diff --git a/src/servermanager/migrations/__init__.py b/src/DjangoBlog/servermanager/__init__.py similarity index 100% rename from src/servermanager/migrations/__init__.py rename to src/DjangoBlog/servermanager/__init__.py diff --git a/src/DjangoBlog/servermanager/__pycache__/MemcacheStorage.cpython-310.pyc b/src/DjangoBlog/servermanager/__pycache__/MemcacheStorage.cpython-310.pyc new file mode 100644 index 0000000..5a07919 Binary files /dev/null and b/src/DjangoBlog/servermanager/__pycache__/MemcacheStorage.cpython-310.pyc differ diff --git a/src/DjangoBlog/servermanager/__pycache__/__init__.cpython-310.pyc b/src/DjangoBlog/servermanager/__pycache__/__init__.cpython-310.pyc new file mode 100644 index 0000000..371b98a Binary files /dev/null and b/src/DjangoBlog/servermanager/__pycache__/__init__.cpython-310.pyc differ diff --git a/src/DjangoBlog/servermanager/__pycache__/admin.cpython-310.pyc b/src/DjangoBlog/servermanager/__pycache__/admin.cpython-310.pyc new file mode 100644 index 0000000..2f8c7dd Binary files /dev/null and b/src/DjangoBlog/servermanager/__pycache__/admin.cpython-310.pyc differ diff --git a/src/DjangoBlog/servermanager/__pycache__/apps.cpython-310.pyc b/src/DjangoBlog/servermanager/__pycache__/apps.cpython-310.pyc new file mode 100644 index 0000000..9d906a9 Binary files /dev/null and b/src/DjangoBlog/servermanager/__pycache__/apps.cpython-310.pyc differ diff --git a/src/DjangoBlog/servermanager/__pycache__/models.cpython-310.pyc b/src/DjangoBlog/servermanager/__pycache__/models.cpython-310.pyc new file mode 100644 index 0000000..635c382 Binary files /dev/null and b/src/DjangoBlog/servermanager/__pycache__/models.cpython-310.pyc differ diff --git a/src/DjangoBlog/servermanager/__pycache__/robot.cpython-310.pyc b/src/DjangoBlog/servermanager/__pycache__/robot.cpython-310.pyc new file mode 100644 index 0000000..ccc8c78 Binary files /dev/null and b/src/DjangoBlog/servermanager/__pycache__/robot.cpython-310.pyc differ diff --git a/src/DjangoBlog/servermanager/__pycache__/urls.cpython-310.pyc b/src/DjangoBlog/servermanager/__pycache__/urls.cpython-310.pyc new file mode 100644 index 0000000..dc3c21e Binary files /dev/null and b/src/DjangoBlog/servermanager/__pycache__/urls.cpython-310.pyc differ diff --git a/src/DjangoBlog/servermanager/admin.py b/src/DjangoBlog/servermanager/admin.py new file mode 100644 index 0000000..f26f4f6 --- /dev/null +++ b/src/DjangoBlog/servermanager/admin.py @@ -0,0 +1,19 @@ +from django.contrib import admin +# Register your models here. + + +class CommandsAdmin(admin.ModelAdmin): + list_display = ('title', 'command', 'describe') + + +class EmailSendLogAdmin(admin.ModelAdmin): + list_display = ('title', 'emailto', 'send_result', 'creation_time') + readonly_fields = ( + 'title', + 'emailto', + 'send_result', + 'creation_time', + 'content') + + def has_add_permission(self, request): + return False diff --git a/src/servermanager/api/__init__.py b/src/DjangoBlog/servermanager/api/__init__.py similarity index 100% rename from src/servermanager/api/__init__.py rename to src/DjangoBlog/servermanager/api/__init__.py diff --git a/src/DjangoBlog/servermanager/api/__pycache__/__init__.cpython-310.pyc b/src/DjangoBlog/servermanager/api/__pycache__/__init__.cpython-310.pyc new file mode 100644 index 0000000..a1b7c7f Binary files /dev/null and b/src/DjangoBlog/servermanager/api/__pycache__/__init__.cpython-310.pyc differ diff --git a/src/DjangoBlog/servermanager/api/__pycache__/blogapi.cpython-310.pyc b/src/DjangoBlog/servermanager/api/__pycache__/blogapi.cpython-310.pyc new file mode 100644 index 0000000..5b664cb Binary files /dev/null and b/src/DjangoBlog/servermanager/api/__pycache__/blogapi.cpython-310.pyc differ diff --git a/src/DjangoBlog/servermanager/api/__pycache__/commonapi.cpython-310.pyc b/src/DjangoBlog/servermanager/api/__pycache__/commonapi.cpython-310.pyc new file mode 100644 index 0000000..3f7ea7c Binary files /dev/null and b/src/DjangoBlog/servermanager/api/__pycache__/commonapi.cpython-310.pyc differ diff --git a/src/DjangoBlog/servermanager/api/blogapi.py b/src/DjangoBlog/servermanager/api/blogapi.py new file mode 100644 index 0000000..1807f2b --- /dev/null +++ b/src/DjangoBlog/servermanager/api/blogapi.py @@ -0,0 +1,51 @@ +# 导入Django-Haystack的搜索查询集类,用于实现全文搜索功能 +from haystack.query import SearchQuerySet +# 导入博客应用的核心数据模型:文章模型(Article)和分类模型(Category) +from blog.models import Article, Category + + +class BlogApi: + """ + 博客系统数据接口封装类 + 统一提供文章搜索、分类查询、分类文章筛选、最新文章获取等核心数据操作 + """ + def __init__(self): + # 初始化搜索查询集,用于后续全文搜索 + self.searchqueryset = SearchQuerySet() + # 执行空查询初始化搜索状态,避免首次搜索异常 + self.searchqueryset.auto_query('') + # 定义每次查询的最大返回数量(8条),控制数据返回规模 + self.__max_takecount__ = 8 + + def search_articles(self, query): + """根据关键词搜索文章 + :param query: 搜索关键词 + :return: 匹配的前8条文章结果(含完整模型数据) + """ + # 按关键词构建搜索查询 + sqs = self.searchqueryset.auto_query(query) + # 加载完整的关联模型数据,避免延迟加载问题 + sqs = sqs.load_all() + # 限制返回结果数量并返回 + return sqs[:self.__max_takecount__] + + def get_category_lists(self): + """获取所有博客分类 + :return: 包含所有分类的查询集 + """ + return Category.objects.all() + + def get_category_articles(self, categoryname): + """根据分类名称筛选该分类下的文章 + :param categoryname: 目标分类名称 + :return: 该分类下的前8条文章(无结果时返回None) + """ + # 按分类名称过滤文章(跨模型匹配Category的name字段) + articles = Article.objects.filter(category__name=categoryname) + return articles[:self.__max_takecount__] if articles else None + + def get_recent_articles(self): + """获取最新发布的文章 + :return: 最新的8条文章查询集(默认按创建时间排序) + """ + return Article.objects.all()[:self.__max_takecount__] \ No newline at end of file diff --git a/src/servermanager/api/commonapi.py b/src/DjangoBlog/servermanager/api/commonapi.py similarity index 100% rename from src/servermanager/api/commonapi.py rename to src/DjangoBlog/servermanager/api/commonapi.py diff --git a/src/servermanager/apps.py b/src/DjangoBlog/servermanager/apps.py similarity index 100% rename from src/servermanager/apps.py rename to src/DjangoBlog/servermanager/apps.py diff --git a/src/servermanager/migrations/0001_initial.py b/src/DjangoBlog/servermanager/migrations/0001_initial.py similarity index 100% rename from src/servermanager/migrations/0001_initial.py rename to src/DjangoBlog/servermanager/migrations/0001_initial.py diff --git a/src/servermanager/migrations/0002_alter_emailsendlog_options_and_more.py b/src/DjangoBlog/servermanager/migrations/0002_alter_emailsendlog_options_and_more.py similarity index 100% rename from src/servermanager/migrations/0002_alter_emailsendlog_options_and_more.py rename to src/DjangoBlog/servermanager/migrations/0002_alter_emailsendlog_options_and_more.py diff --git a/src/DjangoBlog/servermanager/migrations/__init__.py b/src/DjangoBlog/servermanager/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/DjangoBlog/servermanager/migrations/__pycache__/0001_initial.cpython-310.pyc b/src/DjangoBlog/servermanager/migrations/__pycache__/0001_initial.cpython-310.pyc new file mode 100644 index 0000000..d44f8a5 Binary files /dev/null and b/src/DjangoBlog/servermanager/migrations/__pycache__/0001_initial.cpython-310.pyc differ diff --git a/src/DjangoBlog/servermanager/migrations/__pycache__/0002_alter_emailsendlog_options_and_more.cpython-310.pyc b/src/DjangoBlog/servermanager/migrations/__pycache__/0002_alter_emailsendlog_options_and_more.cpython-310.pyc new file mode 100644 index 0000000..6782f35 Binary files /dev/null and b/src/DjangoBlog/servermanager/migrations/__pycache__/0002_alter_emailsendlog_options_and_more.cpython-310.pyc differ diff --git a/src/DjangoBlog/servermanager/migrations/__pycache__/__init__.cpython-310.pyc b/src/DjangoBlog/servermanager/migrations/__pycache__/__init__.cpython-310.pyc new file mode 100644 index 0000000..d785889 Binary files /dev/null and b/src/DjangoBlog/servermanager/migrations/__pycache__/__init__.cpython-310.pyc differ diff --git a/src/servermanager/models.py b/src/DjangoBlog/servermanager/models.py similarity index 100% rename from src/servermanager/models.py rename to src/DjangoBlog/servermanager/models.py diff --git a/src/servermanager/robot.py b/src/DjangoBlog/servermanager/robot.py similarity index 87% rename from src/servermanager/robot.py rename to src/DjangoBlog/servermanager/robot.py index 4714cff..7b45736 100644 --- a/src/servermanager/robot.py +++ b/src/DjangoBlog/servermanager/robot.py @@ -13,7 +13,6 @@ from servermanager.api.blogapi import BlogApi from servermanager.api.commonapi import ChatGPT, CommandHandler from .MemcacheStorage import MemcacheStorage -# 初始化 WeRoBot 实例,设置 token 和会话存储 robot = WeRoBot(token=os.environ.get('DJANGO_WEROBOT_TOKEN') or 'lylinux', enable_session=True) memstorage = MemcacheStorage() @@ -24,12 +23,11 @@ else: os.remove(os.path.join(settings.BASE_DIR, 'werobot_session')) robot.config['SESSION_STORAGE'] = FileStorage(filename='werobot_session') -# 初始化 BlogApi 和 CommandHandler 实例 blogapi = BlogApi() cmd_handler = CommandHandler() logger = logging.getLogger(__name__) -# 将文章转换为 WeRoBot 的 ArticlesReply 格式 + def convert_to_article_reply(articles, message): reply = ArticlesReply(message=message) from blog.templatetags.blog_tags import truncatechars_content @@ -47,7 +45,7 @@ def convert_to_article_reply(articles, message): reply.add_article(article) return reply -# 搜索文章功能,匹配以 '?' 开头的消息 + @robot.filter(re.compile(r"^\?.*")) def search(message, session): s = message.content @@ -60,14 +58,14 @@ def search(message, session): else: return '没有找到相关文章。' -# 获取文章分类目录,匹配 'category' 命令 + @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 -# 获取最近文章,匹配 'recent' 命令 + @robot.filter(re.compile(r'^recent\s*$', re.I)) def recents(message, session): articles = blogapi.get_recent_articles() @@ -77,7 +75,7 @@ def recents(message, session): else: return "暂时还没有文章" -# 帮助信息,匹配 'help' 命令 + @robot.filter(re.compile('^help$', re.I)) def help(message, session): return '''欢迎关注! @@ -99,26 +97,23 @@ def help(message, session): PS:以上标点符号都不支持中文标点~~ ''' -# 天气查询功能(占位),匹配 'weather:' 命令 + @robot.filter(re.compile(r'^weather\:.*$', re.I)) def weather(message, session): return "建设中..." -# 身份证信息查询功能(占位),匹配 'idcard:' 命令 @robot.filter(re.compile(r'^idcard\:.*$', re.I)) def idcard(message, session): return "建设中..." -# 默认消息处理器 @robot.handler def echo(message, session): handler = MessageHandler(message, session) return handler.handler() -# 消息处理类 class MessageHandler: def __init__(self, message, session): userid = message.source @@ -132,38 +127,29 @@ class MessageHandler: 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 save_session(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.save_session() return "退出成功" - - # 进入管理员模式 if info.upper() == 'ADMIN': self.userinfo.isAdmin = True self.save_session() return "输入管理员密码" - - # 验证管理员密码 if self.userinfo.isAdmin and not self.userinfo.isPasswordSet: passwd = settings.WXADMIN if settings.TESTING: @@ -180,8 +166,6 @@ class MessageHandler: self.userinfo.Count += 1 self.save_session() return "验证失败,请重新输入管理员密码:" - - # 管理员命令处理逻辑 if self.userinfo.isAdmin and self.userinfo.isPasswordSet: if self.userinfo.Command != '' and info.upper() == 'Y': return cmd_handler.run(self.userinfo.Command) @@ -192,11 +176,9 @@ class MessageHandler: self.save_session() return "确认执行: " + info + " 命令?" - # 默认调用 ChatGPT 进行聊天 return ChatGPT.chat(info) -# 用户信息类 class WxUserInfo(): def __init__(self): self.isAdmin = False diff --git a/src/servermanager/tests.py b/src/DjangoBlog/servermanager/tests.py similarity index 100% rename from src/servermanager/tests.py rename to src/DjangoBlog/servermanager/tests.py diff --git a/src/servermanager/urls.py b/src/DjangoBlog/servermanager/urls.py similarity index 100% rename from src/servermanager/urls.py rename to src/DjangoBlog/servermanager/urls.py diff --git a/src/servermanager/views.py b/src/DjangoBlog/servermanager/views.py similarity index 100% rename from src/servermanager/views.py rename to src/DjangoBlog/servermanager/views.py diff --git a/src/templates/account/forget_password.html b/src/DjangoBlog/templates/account/forget_password.html similarity index 100% rename from src/templates/account/forget_password.html rename to src/DjangoBlog/templates/account/forget_password.html diff --git a/src/templates/account/login.html b/src/DjangoBlog/templates/account/login.html similarity index 100% rename from src/templates/account/login.html rename to src/DjangoBlog/templates/account/login.html diff --git a/src/templates/account/registration_form.html b/src/DjangoBlog/templates/account/registration_form.html similarity index 100% rename from src/templates/account/registration_form.html rename to src/DjangoBlog/templates/account/registration_form.html diff --git a/src/templates/account/result.html b/src/DjangoBlog/templates/account/result.html similarity index 100% rename from src/templates/account/result.html rename to src/DjangoBlog/templates/account/result.html diff --git a/src/templates/blog/article_archives.html b/src/DjangoBlog/templates/blog/article_archives.html similarity index 100% rename from src/templates/blog/article_archives.html rename to src/DjangoBlog/templates/blog/article_archives.html diff --git a/src/templates/blog/article_detail.html b/src/DjangoBlog/templates/blog/article_detail.html similarity index 100% rename from src/templates/blog/article_detail.html rename to src/DjangoBlog/templates/blog/article_detail.html diff --git a/src/templates/blog/article_index.html b/src/DjangoBlog/templates/blog/article_index.html similarity index 100% rename from src/templates/blog/article_index.html rename to src/DjangoBlog/templates/blog/article_index.html diff --git a/src/templates/blog/error_page.html b/src/DjangoBlog/templates/blog/error_page.html similarity index 100% rename from src/templates/blog/error_page.html rename to src/DjangoBlog/templates/blog/error_page.html diff --git a/src/templates/blog/links_list.html b/src/DjangoBlog/templates/blog/links_list.html similarity index 100% rename from src/templates/blog/links_list.html rename to src/DjangoBlog/templates/blog/links_list.html diff --git a/src/templates/blog/tags/article_info.html b/src/DjangoBlog/templates/blog/tags/article_info.html similarity index 90% rename from src/templates/blog/tags/article_info.html rename to src/DjangoBlog/templates/blog/tags/article_info.html index 65b45fa..3deec44 100644 --- a/src/templates/blog/tags/article_info.html +++ b/src/DjangoBlog/templates/blog/tags/article_info.html @@ -48,7 +48,7 @@
{% if isindex %} - {% render_article_content article True %} + {{ article.body|custom_markdown|escape|truncatechars_content }}

Read more

{% else %} @@ -62,7 +62,7 @@ {% endif %}
- {% render_article_content article False %} + {{ article.body|custom_markdown|escape }}
{% endif %} @@ -71,9 +71,4 @@ {% load_article_metas article user %} - - - -{% if not isindex %} - {% render_plugin_widgets 'article_bottom' article=article %} -{% endif %} \ No newline at end of file + \ No newline at end of file diff --git a/src/templates/blog/tags/article_meta_info.html b/src/DjangoBlog/templates/blog/tags/article_meta_info.html similarity index 84% rename from src/templates/blog/tags/article_meta_info.html rename to src/DjangoBlog/templates/blog/tags/article_meta_info.html index ec8a0f9..cb6111c 100644 --- a/src/templates/blog/tags/article_meta_info.html +++ b/src/DjangoBlog/templates/blog/tags/article_meta_info.html @@ -5,6 +5,9 @@
{% trans 'posted in' %} {{ article.category.name }} + + + {% if article.type == 'a' %} {% if article.tags.all %} @@ -43,14 +46,13 @@ title="{% datetimeformat article.pub_time %}" itemprop="datePublished" content="{% datetimeformat article.pub_time %}" rel="bookmark"> - - - {% if user.is_superuser %} - {% trans 'edit' %} - {% endif %} + + + {% if user.is_superuser %} + {% trans 'edit' %} + {% endif %}
diff --git a/src/templates/blog/tags/article_pagination.html b/src/DjangoBlog/templates/blog/tags/article_pagination.html similarity index 100% rename from src/templates/blog/tags/article_pagination.html rename to src/DjangoBlog/templates/blog/tags/article_pagination.html diff --git a/src/templates/blog/tags/article_tag_list.html b/src/DjangoBlog/templates/blog/tags/article_tag_list.html similarity index 100% rename from src/templates/blog/tags/article_tag_list.html rename to src/DjangoBlog/templates/blog/tags/article_tag_list.html diff --git a/src/templates/blog/tags/breadcrumb.html b/src/DjangoBlog/templates/blog/tags/breadcrumb.html similarity index 100% rename from src/templates/blog/tags/breadcrumb.html rename to src/DjangoBlog/templates/blog/tags/breadcrumb.html diff --git a/src/templates/blog/tags/sidebar.html b/src/DjangoBlog/templates/blog/tags/sidebar.html similarity index 98% rename from src/templates/blog/tags/sidebar.html rename to src/DjangoBlog/templates/blog/tags/sidebar.html index ecb6d20..f70544c 100644 --- a/src/templates/blog/tags/sidebar.html +++ b/src/DjangoBlog/templates/blog/tags/sidebar.html @@ -16,7 +16,7 @@ {% endfor %} diff --git a/src/templates/comments/tags/comment_item.html b/src/DjangoBlog/templates/comments/tags/comment_item.html similarity index 84% rename from src/templates/comments/tags/comment_item.html rename to src/DjangoBlog/templates/comments/tags/comment_item.html index 0693649..ebb0388 100644 --- a/src/templates/comments/tags/comment_item.html +++ b/src/DjangoBlog/templates/comments/tags/comment_item.html @@ -2,13 +2,10 @@
  • - {{ comment_item.author.username }}的头像 + class="avatar avatar-96 photo" height="96" width="96">
    - -{% compress js %} - - - - - {% block compress_js %} - {% endblock %} - - {% plugin_compressed_js %} -{% endcompress %} - - - - -{% block footer %} -{% endblock %} - - -{% plugin_body_resources %} +
    + + {% compress js %} + + + + + + {% block compress_js %} + {% endblock %} + {% endcompress %} + {% block footer %} + {% endblock %} +
    diff --git a/src/templates/share_layout/base_account.html b/src/DjangoBlog/templates/share_layout/base_account.html similarity index 100% rename from src/templates/share_layout/base_account.html rename to src/DjangoBlog/templates/share_layout/base_account.html diff --git a/src/templates/share_layout/footer.html b/src/DjangoBlog/templates/share_layout/footer.html similarity index 100% rename from src/templates/share_layout/footer.html rename to src/DjangoBlog/templates/share_layout/footer.html diff --git a/src/templates/share_layout/nav.html b/src/DjangoBlog/templates/share_layout/nav.html similarity index 100% rename from src/templates/share_layout/nav.html rename to src/DjangoBlog/templates/share_layout/nav.html diff --git a/src/templates/share_layout/nav_node.html b/src/DjangoBlog/templates/share_layout/nav_node.html similarity index 100% rename from src/templates/share_layout/nav_node.html rename to src/DjangoBlog/templates/share_layout/nav_node.html diff --git a/src/accounts/admin.py b/src/accounts/admin.py deleted file mode 100644 index e77ec2b..0000000 --- a/src/accounts/admin.py +++ /dev/null @@ -1,85 +0,0 @@ -from django import forms -from django.contrib.auth.admin import UserAdmin -from django.contrib.auth.forms import UserChangeForm -from django.contrib.auth.forms import UsernameField -from django.utils.translation import gettext_lazy as _ - -# 注册模型到Django管理后台 -from .models import BlogUser - - -# 自定义用户创建表单,用于在Django管理后台创建新用户 -class BlogUserCreationForm(forms.ModelForm): - # 定义两个密码字段,分别用于输入密码和确认密码 - password1 = forms.CharField(label=_('password'), widget=forms.PasswordInput) # 密码输入框 - password2 = forms.CharField(label=_('Enter password again'), widget=forms.PasswordInput) # 确认密码输入框 - - class Meta: - # 指定表单对应的模型 - model = BlogUser - # 表单中包含的字段 - fields = ('email',) - - def clean_password2(self): - """ - 验证两个密码输入是否一致 - """ - password1 = self.cleaned_data.get("password1") # 获取第一个密码 - password2 = self.cleaned_data.get("password2") # 获取第二个密码 - if password1 and password2 and password1 != password2: - # 如果两个密码不一致,抛出验证错误 - raise forms.ValidationError(_("passwords do not match")) - return password2 - - def save(self, commit=True): - """ - 保存用户实例,并将密码以哈希格式存储 - """ - user = super().save(commit=False) # 获取未保存的用户实例 - user.set_password(self.cleaned_data["password1"]) # 设置哈希密码 - if commit: - user.source = 'adminsite' # 设置用户来源为管理后台 - user.save() # 保存用户实例到数据库 - return user - - -# 自定义用户修改表单,用于在Django管理后台修改用户信息 -class BlogUserChangeForm(UserChangeForm): - class Meta: - # 指定表单对应的模型 - model = BlogUser - # 表单中包含的所有字段 - fields = '__all__' - # 自定义字段类型 - field_classes = {'username': UsernameField} - - def __init__(self, *args, **kwargs): - """ - 初始化表单 - """ - super().__init__(*args, **kwargs) - - -# 自定义用户管理类,用于在Django管理后台管理用户 -class BlogUserAdmin(UserAdmin): - form = BlogUserChangeForm - add_form = BlogUserCreationForm - list_display = ( - 'id', - 'nickname', - 'username', - 'email', - 'last_login', - 'date_joined', - 'source' - ) - list_display_links = ('id', 'username') - ordering = ('-id',) - search_fields = ('username', 'nickname', 'email') - - fieldsets = ( - (None, {'fields': ('username', 'password')}), - (_('Personal info'), {'fields': ('email', 'nickname')}), - (_('Permissions'), {'fields': ('is_active', 'is_staff', 'is_superuser', 'groups', 'user_permissions')}), - (_('Important dates'), {'fields': ('last_login', 'date_joined')}), - ) \ No newline at end of file diff --git a/src/accounts/models.py b/src/accounts/models.py deleted file mode 100644 index 005b248..0000000 --- a/src/accounts/models.py +++ /dev/null @@ -1,48 +0,0 @@ -from django.contrib.auth.models import AbstractUser -from django.db import models -from django.urls import reverse -from django.utils.timezone import now -from django.utils.translation import gettext_lazy as _ -from djangoblog.utils import get_current_site - - -# 创建自定义用户模型 -class BlogUser(AbstractUser): - # 用户昵称字段,允许为空,最大长度为100 - nickname = models.CharField(_('nick name'), max_length=100, blank=True) - # 用户创建时间字段,默认为当前时间 - creation_time = models.DateTimeField(_('creation time'), default=now) - # 用户最后修改时间字段,默认为当前时间 - last_modify_time = models.DateTimeField(_('last modify time'), default=now) - # 用户来源字段,用于记录用户的创建来源,允许为空,最大长度为100 - source = models.CharField(_('create source'), max_length=100, blank=True) - - def get_absolute_url(self): - """ - 获取用户的绝对URL,用于跳转到用户的详情页面 - """ - return reverse( - 'blog:author_detail', kwargs={ - 'author_name': self.username}) # 根据用户名生成URL - - def __str__(self): - """ - 定义对象的字符串表示,返回用户的邮箱 - """ - return self.email - - def get_full_url(self): - """ - 获取用户的完整URL,包括域名和路径 - """ - site = get_current_site().domain # 获取当前站点的域名 - url = "https://{site}{path}".format(site=site, - path=self.get_absolute_url()) # 拼接完整URL - return url - - class Meta: - # 定义模型的元数据 - ordering = ['-id'] # 默认按ID降序排列 - verbose_name = _('user') # 模型的单数名称 - verbose_name_plural = verbose_name # 模型的复数名称 - get_latest_by = 'id' # 获取最新记录时使用的字段 \ No newline at end of file diff --git a/src/accounts/user_login_backend.py b/src/accounts/user_login_backend.py deleted file mode 100644 index 749c24b..0000000 --- a/src/accounts/user_login_backend.py +++ /dev/null @@ -1,18 +0,0 @@ -# filepath: f:\DjangoBlog\src\accounts\user_login_backend.py -from django.contrib.auth.backends import ModelBackend -from django.contrib.auth import get_user_model -from django.db import models -User = get_user_model() - -class EmailOrUsernameModelBackend(ModelBackend): - """ - 自定义认证后端,支持通过用户名或邮箱登录 - """ - def authenticate(self, request, username=None, password=None, **kwargs): - try: - # 尝试通过用户名或邮箱获取用户 - user = User.objects.get(models.Q(username=username) | models.Q(email=username)) - if user.check_password(password): - return user - except User.DoesNotExist: - return None \ No newline at end of file diff --git a/src/blog/admin.py b/src/blog/admin.py deleted file mode 100644 index 6911be7..0000000 --- a/src/blog/admin.py +++ /dev/null @@ -1,151 +0,0 @@ -from django import forms -from django.contrib import admin -from django.contrib.auth import get_user_model -from django.urls import reverse -from django.utils.html import format_html -from django.utils.translation import gettext_lazy as _ - -# 导入相关模型 -from .models import Article, Category, Tag, Links, SideBar, BlogSettings - - -# 自定义表单,用于文章模型的管理界面 -class ArticleForm(forms.ModelForm): - # 可以在此处自定义字段的表单小部件 - class Meta: - model = Article - fields = '__all__' - - -# 定义批量操作函数 -def makr_article_publish(modeladmin, request, queryset): - # 将选中的文章状态更新为“已发布” - queryset.update(status='p') - - -def draft_article(modeladmin, request, queryset): - # 将选中的文章状态更新为“草稿” - queryset.update(status='d') - - -def close_article_commentstatus(modeladmin, request, queryset): - # 将选中的文章评论状态更新为“关闭” - queryset.update(comment_status='c') - - -def open_article_commentstatus(modeladmin, request, queryset): - # 将选中的文章评论状态更新为“开启” - queryset.update(comment_status='o') - - -# 为批量操作函数添加描述 -makr_article_publish.short_description = _('Publish selected articles') -draft_article.short_description = _('Draft selected articles') -close_article_commentstatus.short_description = _('Close article comments') -open_article_commentstatus.short_description = _('Open article comments') - - -# 自定义文章管理界面 -class ArticlelAdmin(admin.ModelAdmin): - # 每页显示的记录数 - list_per_page = 20 - # 可搜索的字段 - search_fields = ('body', 'title') - # 使用自定义表单 - form = ArticleForm - # 列表显示的字段 - list_display = ( - 'id', - 'title', - 'author', - 'link_to_category', - 'creation_time', - 'views', - 'status', - 'type', - 'article_order') - # 可点击进入详情的字段 - list_display_links = ('id', 'title') - # 列表过滤器 - list_filter = ('status', 'type', 'category') - # 按日期层级显示 - date_hierarchy = 'creation_time' - # 多对多字段的水平过滤器 - filter_horizontal = ('tags',) - # 排除的字段 - exclude = ('creation_time', 'last_modify_time') - # 是否允许在站点上查看 - view_on_site = True - # 批量操作 - actions = [ - makr_article_publish, - draft_article, - close_article_commentstatus, - open_article_commentstatus] - # 使用原始 ID 字段显示外键 - raw_id_fields = ('author', 'category',) - - # 自定义显示分类链接 - def link_to_category(self, obj): - info = (obj.category._meta.app_label, obj.category._meta.model_name) - link = reverse('admin:%s_%s_change' % info, args=(obj.category.id,)) - return format_html(u'
    %s' % (link, obj.category.name)) - - link_to_category.short_description = _('category') - - # 自定义表单的查询集 - 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) - - # 自定义站点查看 URL - def get_view_on_site_url(self, obj=None): - if obj: - # 如果对象存在,返回其完整 URL - url = obj.get_full_url() - return url - else: - # 否则返回当前站点的域名 - from djangoblog.utils import get_current_site - site = get_current_site().domain - return site - - -# 自定义标签管理界面 -class TagAdmin(admin.ModelAdmin): - # 排除的字段 - exclude = ('slug', 'last_mod_time', 'creation_time') - - -# 自定义分类管理界面 -class CategoryAdmin(admin.ModelAdmin): - # 列表显示的字段 - list_display = ('name', 'parent_category', 'index') - # 排除的字段 - exclude = ('slug', 'last_mod_time', 'creation_time') - - -# 自定义链接管理界面 -class LinksAdmin(admin.ModelAdmin): - # 排除的字段 - exclude = ('last_mod_time', 'creation_time') - - -# 自定义侧边栏管理界面 -class SideBarAdmin(admin.ModelAdmin): - # 列表显示的字段 - list_display = ('name', 'content', 'is_enable', 'sequence') - # 排除的字段 - exclude = ('last_mod_time', 'creation_time') - - -# 自定义博客设置管理界面 -class BlogSettingsAdmin(admin.ModelAdmin): - pass diff --git a/src/blog/middleware.py b/src/blog/middleware.py deleted file mode 100644 index d67fcee..0000000 --- a/src/blog/middleware.py +++ /dev/null @@ -1,71 +0,0 @@ -import logging -import time - -from ipware import get_client_ip # 用于获取客户端的 IP 地址 -from user_agents import parse # 用于解析用户代理字符串 - -from blog.documents import ELASTICSEARCH_ENABLED, ElaspedTimeDocumentManager # 导入 Elasticsearch 配置和文档管理器 - -logger = logging.getLogger(__name__) # 设置日志记录器 - - -class OnlineMiddleware(object): - def __init__(self, get_response=None): - """ - 初始化中间件,接收 Django 的 get_response 方法。 - """ - self.get_response = get_response - super().__init__() - - def __call__(self, request): - """ - 中间件的主要逻辑,用于记录页面渲染时间并将其存储到 Elasticsearch。 - """ - # 记录请求开始时间 - start_time = time.time() - - # 调用下一个中间件或视图 - response = self.get_response(request) - - # 获取 HTTP 用户代理字符串 - http_user_agent = request.META.get('HTTP_USER_AGENT', '') - - # 获取客户端 IP 地址 - ip, _ = get_client_ip(request) - - # 解析用户代理字符串 - user_agent = parse(http_user_agent) - - # 如果响应不是流式的,处理渲染时间 - if not response.streaming: - try: - # 计算页面渲染时间 - cast_time = time.time() - start_time - - # 如果启用了 Elasticsearch,将数据存储到 Elasticsearch - if ELASTICSEARCH_ENABLED: - time_taken = round((cast_time) * 1000, 2) # 将时间转换为毫秒并保留两位小数 - url = request.path # 获取请求的 URL - - # 获取当前时间 - from django.utils import timezone - - # 创建 Elasticsearch 文档 - ElaspedTimeDocumentManager.create( - url=url, - time_taken=time_taken, - log_datetime=timezone.now(), - useragent=user_agent, - ip=ip - ) - - # 替换响应内容中的占位符 为渲染时间 - response.content = response.content.replace( - b'', str.encode(str(cast_time)[:5]) - ) - except Exception as e: - # 如果发生异常,记录错误日志 - logger.error("Error OnlineMiddleware: %s" % e) - - # 返回响应 - return response \ No newline at end of file diff --git a/src/blog/static/blog/fonts/memtYaGs126MiZpBA-UFUIcVXSCEkx2cmqvXlWqW106F15M.woff2 b/src/blog/static/blog/fonts/memtYaGs126MiZpBA-UFUIcVXSCEkx2cmqvXlWqW106F15M.woff2 deleted file mode 100644 index 0fb066c..0000000 Binary files a/src/blog/static/blog/fonts/memtYaGs126MiZpBA-UFUIcVXSCEkx2cmqvXlWqW106F15M.woff2 and /dev/null differ diff --git a/src/blog/static/blog/fonts/memtYaGs126MiZpBA-UFUIcVXSCEkx2cmqvXlWqWt06F15M.woff2 b/src/blog/static/blog/fonts/memtYaGs126MiZpBA-UFUIcVXSCEkx2cmqvXlWqWt06F15M.woff2 deleted file mode 100644 index bc2aea0..0000000 Binary files a/src/blog/static/blog/fonts/memtYaGs126MiZpBA-UFUIcVXSCEkx2cmqvXlWqWt06F15M.woff2 and /dev/null differ diff --git a/src/blog/static/blog/fonts/memtYaGs126MiZpBA-UFUIcVXSCEkx2cmqvXlWqWtE6F15M.woff2 b/src/blog/static/blog/fonts/memtYaGs126MiZpBA-UFUIcVXSCEkx2cmqvXlWqWtE6F15M.woff2 deleted file mode 100644 index fcce594..0000000 Binary files a/src/blog/static/blog/fonts/memtYaGs126MiZpBA-UFUIcVXSCEkx2cmqvXlWqWtE6F15M.woff2 and /dev/null differ diff --git a/src/blog/static/blog/fonts/memtYaGs126MiZpBA-UFUIcVXSCEkx2cmqvXlWqWtU6F15M.woff2 b/src/blog/static/blog/fonts/memtYaGs126MiZpBA-UFUIcVXSCEkx2cmqvXlWqWtU6F15M.woff2 deleted file mode 100644 index ffc8e9c..0000000 Binary files a/src/blog/static/blog/fonts/memtYaGs126MiZpBA-UFUIcVXSCEkx2cmqvXlWqWtU6F15M.woff2 and /dev/null differ diff --git a/src/blog/static/blog/fonts/memtYaGs126MiZpBA-UFUIcVXSCEkx2cmqvXlWqWtk6F15M.woff2 b/src/blog/static/blog/fonts/memtYaGs126MiZpBA-UFUIcVXSCEkx2cmqvXlWqWtk6F15M.woff2 deleted file mode 100644 index 6375e9c..0000000 Binary files a/src/blog/static/blog/fonts/memtYaGs126MiZpBA-UFUIcVXSCEkx2cmqvXlWqWtk6F15M.woff2 and /dev/null differ diff --git a/src/blog/static/blog/fonts/memtYaGs126MiZpBA-UFUIcVXSCEkx2cmqvXlWqWu06F15M.woff2 b/src/blog/static/blog/fonts/memtYaGs126MiZpBA-UFUIcVXSCEkx2cmqvXlWqWu06F15M.woff2 deleted file mode 100644 index 2e849f6..0000000 Binary files a/src/blog/static/blog/fonts/memtYaGs126MiZpBA-UFUIcVXSCEkx2cmqvXlWqWu06F15M.woff2 and /dev/null differ diff --git a/src/blog/static/blog/fonts/memtYaGs126MiZpBA-UFUIcVXSCEkx2cmqvXlWqWuU6F.woff2 b/src/blog/static/blog/fonts/memtYaGs126MiZpBA-UFUIcVXSCEkx2cmqvXlWqWuU6F.woff2 deleted file mode 100644 index 5de3fea..0000000 Binary files a/src/blog/static/blog/fonts/memtYaGs126MiZpBA-UFUIcVXSCEkx2cmqvXlWqWuU6F.woff2 and /dev/null differ diff --git a/src/blog/static/blog/fonts/memtYaGs126MiZpBA-UFUIcVXSCEkx2cmqvXlWqWuk6F15M.woff2 b/src/blog/static/blog/fonts/memtYaGs126MiZpBA-UFUIcVXSCEkx2cmqvXlWqWuk6F15M.woff2 deleted file mode 100644 index e5c936b..0000000 Binary files a/src/blog/static/blog/fonts/memtYaGs126MiZpBA-UFUIcVXSCEkx2cmqvXlWqWuk6F15M.woff2 and /dev/null differ diff --git a/src/blog/static/blog/fonts/memtYaGs126MiZpBA-UFUIcVXSCEkx2cmqvXlWqWvU6F15M.woff2 b/src/blog/static/blog/fonts/memtYaGs126MiZpBA-UFUIcVXSCEkx2cmqvXlWqWvU6F15M.woff2 deleted file mode 100644 index 5cf8aff..0000000 Binary files a/src/blog/static/blog/fonts/memtYaGs126MiZpBA-UFUIcVXSCEkx2cmqvXlWqWvU6F15M.woff2 and /dev/null differ diff --git a/src/blog/static/blog/fonts/memtYaGs126MiZpBA-UFUIcVXSCEkx2cmqvXlWqWxU6F15M.woff2 b/src/blog/static/blog/fonts/memtYaGs126MiZpBA-UFUIcVXSCEkx2cmqvXlWqWxU6F15M.woff2 deleted file mode 100644 index bdc12e8..0000000 Binary files a/src/blog/static/blog/fonts/memtYaGs126MiZpBA-UFUIcVXSCEkx2cmqvXlWqWxU6F15M.woff2 and /dev/null differ diff --git a/src/blog/static/blog/fonts/memvYaGs126MiZpBA-UvWbX2vVnXBbObj2OVTS-muw.woff2 b/src/blog/static/blog/fonts/memvYaGs126MiZpBA-UvWbX2vVnXBbObj2OVTS-muw.woff2 deleted file mode 100644 index b5d54e7..0000000 Binary files a/src/blog/static/blog/fonts/memvYaGs126MiZpBA-UvWbX2vVnXBbObj2OVTS-muw.woff2 and /dev/null differ diff --git a/src/blog/static/blog/fonts/memvYaGs126MiZpBA-UvWbX2vVnXBbObj2OVTS2mu1aB.woff2 b/src/blog/static/blog/fonts/memvYaGs126MiZpBA-UvWbX2vVnXBbObj2OVTS2mu1aB.woff2 deleted file mode 100644 index bed5b67..0000000 Binary files a/src/blog/static/blog/fonts/memvYaGs126MiZpBA-UvWbX2vVnXBbObj2OVTS2mu1aB.woff2 and /dev/null differ diff --git a/src/blog/static/blog/fonts/memvYaGs126MiZpBA-UvWbX2vVnXBbObj2OVTSCmu1aB.woff2 b/src/blog/static/blog/fonts/memvYaGs126MiZpBA-UvWbX2vVnXBbObj2OVTSCmu1aB.woff2 deleted file mode 100644 index 9164ccb..0000000 Binary files a/src/blog/static/blog/fonts/memvYaGs126MiZpBA-UvWbX2vVnXBbObj2OVTSCmu1aB.woff2 and /dev/null differ diff --git a/src/blog/static/blog/fonts/memvYaGs126MiZpBA-UvWbX2vVnXBbObj2OVTSGmu1aB.woff2 b/src/blog/static/blog/fonts/memvYaGs126MiZpBA-UvWbX2vVnXBbObj2OVTSGmu1aB.woff2 deleted file mode 100644 index 08bed85..0000000 Binary files a/src/blog/static/blog/fonts/memvYaGs126MiZpBA-UvWbX2vVnXBbObj2OVTSGmu1aB.woff2 and /dev/null differ diff --git a/src/blog/static/blog/fonts/memvYaGs126MiZpBA-UvWbX2vVnXBbObj2OVTSKmu1aB.woff2 b/src/blog/static/blog/fonts/memvYaGs126MiZpBA-UvWbX2vVnXBbObj2OVTSKmu1aB.woff2 deleted file mode 100644 index 307b214..0000000 Binary files a/src/blog/static/blog/fonts/memvYaGs126MiZpBA-UvWbX2vVnXBbObj2OVTSKmu1aB.woff2 and /dev/null differ diff --git a/src/blog/static/blog/fonts/memvYaGs126MiZpBA-UvWbX2vVnXBbObj2OVTSOmu1aB.woff2 b/src/blog/static/blog/fonts/memvYaGs126MiZpBA-UvWbX2vVnXBbObj2OVTSOmu1aB.woff2 deleted file mode 100644 index 0b0b3a4..0000000 Binary files a/src/blog/static/blog/fonts/memvYaGs126MiZpBA-UvWbX2vVnXBbObj2OVTSOmu1aB.woff2 and /dev/null differ diff --git a/src/blog/static/blog/fonts/memvYaGs126MiZpBA-UvWbX2vVnXBbObj2OVTSumu1aB.woff2 b/src/blog/static/blog/fonts/memvYaGs126MiZpBA-UvWbX2vVnXBbObj2OVTSumu1aB.woff2 deleted file mode 100644 index 4bce1d0..0000000 Binary files a/src/blog/static/blog/fonts/memvYaGs126MiZpBA-UvWbX2vVnXBbObj2OVTSumu1aB.woff2 and /dev/null differ diff --git a/src/blog/static/blog/fonts/memvYaGs126MiZpBA-UvWbX2vVnXBbObj2OVTSymu1aB.woff2 b/src/blog/static/blog/fonts/memvYaGs126MiZpBA-UvWbX2vVnXBbObj2OVTSymu1aB.woff2 deleted file mode 100644 index 5bd7b8f..0000000 Binary files a/src/blog/static/blog/fonts/memvYaGs126MiZpBA-UvWbX2vVnXBbObj2OVTSymu1aB.woff2 and /dev/null differ diff --git a/src/blog/static/blog/fonts/memvYaGs126MiZpBA-UvWbX2vVnXBbObj2OVTUGmu1aB.woff2 b/src/blog/static/blog/fonts/memvYaGs126MiZpBA-UvWbX2vVnXBbObj2OVTUGmu1aB.woff2 deleted file mode 100644 index b969602..0000000 Binary files a/src/blog/static/blog/fonts/memvYaGs126MiZpBA-UvWbX2vVnXBbObj2OVTUGmu1aB.woff2 and /dev/null differ diff --git a/src/blog/static/blog/fonts/memvYaGs126MiZpBA-UvWbX2vVnXBbObj2OVTVOmu1aB.woff2 b/src/blog/static/blog/fonts/memvYaGs126MiZpBA-UvWbX2vVnXBbObj2OVTVOmu1aB.woff2 deleted file mode 100644 index a804b10..0000000 Binary files a/src/blog/static/blog/fonts/memvYaGs126MiZpBA-UvWbX2vVnXBbObj2OVTVOmu1aB.woff2 and /dev/null differ diff --git a/src/blog/static/blog/fonts/open-sans.css b/src/blog/static/blog/fonts/open-sans.css deleted file mode 100644 index e6dd4a9..0000000 --- a/src/blog/static/blog/fonts/open-sans.css +++ /dev/null @@ -1,600 +0,0 @@ -/* cyrillic-ext */ -@font-face { - font-family: 'Open Sans'; - font-style: italic; - font-weight: 300; - font-stretch: 100%; - font-display: swap; - src: url(memtYaGs126MiZpBA-UFUIcVXSCEkx2cmqvXlWqWtE6F15M.woff2) format('woff2'); - unicode-range: U+0460-052F, U+1C80-1C8A, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F; -} -/* cyrillic */ -@font-face { - font-family: 'Open Sans'; - font-style: italic; - font-weight: 300; - font-stretch: 100%; - font-display: swap; - src: url(memtYaGs126MiZpBA-UFUIcVXSCEkx2cmqvXlWqWvU6F15M.woff2) format('woff2'); - unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116; -} -/* greek-ext */ -@font-face { - font-family: 'Open Sans'; - font-style: italic; - font-weight: 300; - font-stretch: 100%; - font-display: swap; - src: url(memtYaGs126MiZpBA-UFUIcVXSCEkx2cmqvXlWqWtU6F15M.woff2) format('woff2'); - unicode-range: U+1F00-1FFF; -} -/* greek */ -@font-face { - font-family: 'Open Sans'; - font-style: italic; - font-weight: 300; - font-stretch: 100%; - font-display: swap; - src: url(memtYaGs126MiZpBA-UFUIcVXSCEkx2cmqvXlWqWuk6F15M.woff2) format('woff2'); - unicode-range: U+0370-0377, U+037A-037F, U+0384-038A, U+038C, U+038E-03A1, U+03A3-03FF; -} -/* hebrew */ -@font-face { - font-family: 'Open Sans'; - font-style: italic; - font-weight: 300; - font-stretch: 100%; - font-display: swap; - src: url(memtYaGs126MiZpBA-UFUIcVXSCEkx2cmqvXlWqWu06F15M.woff2) format('woff2'); - unicode-range: U+0307-0308, U+0590-05FF, U+200C-2010, U+20AA, U+25CC, U+FB1D-FB4F; -} -/* math */ -@font-face { - font-family: 'Open Sans'; - font-style: italic; - font-weight: 300; - font-stretch: 100%; - font-display: swap; - src: url(memtYaGs126MiZpBA-UFUIcVXSCEkx2cmqvXlWqWxU6F15M.woff2) format('woff2'); - unicode-range: U+0302-0303, U+0305, U+0307-0308, U+0310, U+0312, U+0315, U+031A, U+0326-0327, U+032C, U+032F-0330, U+0332-0333, U+0338, U+033A, U+0346, U+034D, U+0391-03A1, U+03A3-03A9, U+03B1-03C9, U+03D1, U+03D5-03D6, U+03F0-03F1, U+03F4-03F5, U+2016-2017, U+2034-2038, U+203C, U+2040, U+2043, U+2047, U+2050, U+2057, U+205F, U+2070-2071, U+2074-208E, U+2090-209C, U+20D0-20DC, U+20E1, U+20E5-20EF, U+2100-2112, U+2114-2115, U+2117-2121, U+2123-214F, U+2190, U+2192, U+2194-21AE, U+21B0-21E5, U+21F1-21F2, U+21F4-2211, U+2213-2214, U+2216-22FF, U+2308-230B, U+2310, U+2319, U+231C-2321, U+2336-237A, U+237C, U+2395, U+239B-23B7, U+23D0, U+23DC-23E1, U+2474-2475, U+25AF, U+25B3, U+25B7, U+25BD, U+25C1, U+25CA, U+25CC, U+25FB, U+266D-266F, U+27C0-27FF, U+2900-2AFF, U+2B0E-2B11, U+2B30-2B4C, U+2BFE, U+3030, U+FF5B, U+FF5D, U+1D400-1D7FF, U+1EE00-1EEFF; -} -/* symbols */ -@font-face { - font-family: 'Open Sans'; - font-style: italic; - font-weight: 300; - font-stretch: 100%; - font-display: swap; - src: url(memtYaGs126MiZpBA-UFUIcVXSCEkx2cmqvXlWqW106F15M.woff2) format('woff2'); - unicode-range: U+0001-000C, U+000E-001F, U+007F-009F, U+20DD-20E0, U+20E2-20E4, U+2150-218F, U+2190, U+2192, U+2194-2199, U+21AF, U+21E6-21F0, U+21F3, U+2218-2219, U+2299, U+22C4-22C6, U+2300-243F, U+2440-244A, U+2460-24FF, U+25A0-27BF, U+2800-28FF, U+2921-2922, U+2981, U+29BF, U+29EB, U+2B00-2BFF, U+4DC0-4DFF, U+FFF9-FFFB, U+10140-1018E, U+10190-1019C, U+101A0, U+101D0-101FD, U+102E0-102FB, U+10E60-10E7E, U+1D2C0-1D2D3, U+1D2E0-1D37F, U+1F000-1F0FF, U+1F100-1F1AD, U+1F1E6-1F1FF, U+1F30D-1F30F, U+1F315, U+1F31C, U+1F31E, U+1F320-1F32C, U+1F336, U+1F378, U+1F37D, U+1F382, U+1F393-1F39F, U+1F3A7-1F3A8, U+1F3AC-1F3AF, U+1F3C2, U+1F3C4-1F3C6, U+1F3CA-1F3CE, U+1F3D4-1F3E0, U+1F3ED, U+1F3F1-1F3F3, U+1F3F5-1F3F7, U+1F408, U+1F415, U+1F41F, U+1F426, U+1F43F, U+1F441-1F442, U+1F444, U+1F446-1F449, U+1F44C-1F44E, U+1F453, U+1F46A, U+1F47D, U+1F4A3, U+1F4B0, U+1F4B3, U+1F4B9, U+1F4BB, U+1F4BF, U+1F4C8-1F4CB, U+1F4D6, U+1F4DA, U+1F4DF, U+1F4E3-1F4E6, U+1F4EA-1F4ED, U+1F4F7, U+1F4F9-1F4FB, U+1F4FD-1F4FE, U+1F503, U+1F507-1F50B, U+1F50D, U+1F512-1F513, U+1F53E-1F54A, U+1F54F-1F5FA, U+1F610, U+1F650-1F67F, U+1F687, U+1F68D, U+1F691, U+1F694, U+1F698, U+1F6AD, U+1F6B2, U+1F6B9-1F6BA, U+1F6BC, U+1F6C6-1F6CF, U+1F6D3-1F6D7, U+1F6E0-1F6EA, U+1F6F0-1F6F3, U+1F6F7-1F6FC, U+1F700-1F7FF, U+1F800-1F80B, U+1F810-1F847, U+1F850-1F859, U+1F860-1F887, U+1F890-1F8AD, U+1F8B0-1F8BB, U+1F8C0-1F8C1, U+1F900-1F90B, U+1F93B, U+1F946, U+1F984, U+1F996, U+1F9E9, U+1FA00-1FA6F, U+1FA70-1FA7C, U+1FA80-1FA89, U+1FA8F-1FAC6, U+1FACE-1FADC, U+1FADF-1FAE9, U+1FAF0-1FAF8, U+1FB00-1FBFF; -} -/* vietnamese */ -@font-face { - font-family: 'Open Sans'; - font-style: italic; - font-weight: 300; - font-stretch: 100%; - font-display: swap; - src: url(memtYaGs126MiZpBA-UFUIcVXSCEkx2cmqvXlWqWtk6F15M.woff2) format('woff2'); - unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+0300-0301, U+0303-0304, U+0308-0309, U+0323, U+0329, U+1EA0-1EF9, U+20AB; -} -/* latin-ext */ -@font-face { - font-family: 'Open Sans'; - font-style: italic; - font-weight: 300; - font-stretch: 100%; - font-display: swap; - src: url(memtYaGs126MiZpBA-UFUIcVXSCEkx2cmqvXlWqWt06F15M.woff2) format('woff2'); - unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF; -} -/* latin */ -@font-face { - font-family: 'Open Sans'; - font-style: italic; - font-weight: 300; - font-stretch: 100%; - font-display: swap; - src: url(memtYaGs126MiZpBA-UFUIcVXSCEkx2cmqvXlWqWuU6F.woff2) format('woff2'); - unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD; -} -/* cyrillic-ext */ -@font-face { - font-family: 'Open Sans'; - font-style: italic; - font-weight: 400; - font-stretch: 100%; - font-display: swap; - src: url(memtYaGs126MiZpBA-UFUIcVXSCEkx2cmqvXlWqWtE6F15M.woff2) format('woff2'); - unicode-range: U+0460-052F, U+1C80-1C8A, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F; -} -/* cyrillic */ -@font-face { - font-family: 'Open Sans'; - font-style: italic; - font-weight: 400; - font-stretch: 100%; - font-display: swap; - src: url(memtYaGs126MiZpBA-UFUIcVXSCEkx2cmqvXlWqWvU6F15M.woff2) format('woff2'); - unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116; -} -/* greek-ext */ -@font-face { - font-family: 'Open Sans'; - font-style: italic; - font-weight: 400; - font-stretch: 100%; - font-display: swap; - src: url(memtYaGs126MiZpBA-UFUIcVXSCEkx2cmqvXlWqWtU6F15M.woff2) format('woff2'); - unicode-range: U+1F00-1FFF; -} -/* greek */ -@font-face { - font-family: 'Open Sans'; - font-style: italic; - font-weight: 400; - font-stretch: 100%; - font-display: swap; - src: url(memtYaGs126MiZpBA-UFUIcVXSCEkx2cmqvXlWqWuk6F15M.woff2) format('woff2'); - unicode-range: U+0370-0377, U+037A-037F, U+0384-038A, U+038C, U+038E-03A1, U+03A3-03FF; -} -/* hebrew */ -@font-face { - font-family: 'Open Sans'; - font-style: italic; - font-weight: 400; - font-stretch: 100%; - font-display: swap; - src: url(memtYaGs126MiZpBA-UFUIcVXSCEkx2cmqvXlWqWu06F15M.woff2) format('woff2'); - unicode-range: U+0307-0308, U+0590-05FF, U+200C-2010, U+20AA, U+25CC, U+FB1D-FB4F; -} -/* math */ -@font-face { - font-family: 'Open Sans'; - font-style: italic; - font-weight: 400; - font-stretch: 100%; - font-display: swap; - src: url(memtYaGs126MiZpBA-UFUIcVXSCEkx2cmqvXlWqWxU6F15M.woff2) format('woff2'); - unicode-range: U+0302-0303, U+0305, U+0307-0308, U+0310, U+0312, U+0315, U+031A, U+0326-0327, U+032C, U+032F-0330, U+0332-0333, U+0338, U+033A, U+0346, U+034D, U+0391-03A1, U+03A3-03A9, U+03B1-03C9, U+03D1, U+03D5-03D6, U+03F0-03F1, U+03F4-03F5, U+2016-2017, U+2034-2038, U+203C, U+2040, U+2043, U+2047, U+2050, U+2057, U+205F, U+2070-2071, U+2074-208E, U+2090-209C, U+20D0-20DC, U+20E1, U+20E5-20EF, U+2100-2112, U+2114-2115, U+2117-2121, U+2123-214F, U+2190, U+2192, U+2194-21AE, U+21B0-21E5, U+21F1-21F2, U+21F4-2211, U+2213-2214, U+2216-22FF, U+2308-230B, U+2310, U+2319, U+231C-2321, U+2336-237A, U+237C, U+2395, U+239B-23B7, U+23D0, U+23DC-23E1, U+2474-2475, U+25AF, U+25B3, U+25B7, U+25BD, U+25C1, U+25CA, U+25CC, U+25FB, U+266D-266F, U+27C0-27FF, U+2900-2AFF, U+2B0E-2B11, U+2B30-2B4C, U+2BFE, U+3030, U+FF5B, U+FF5D, U+1D400-1D7FF, U+1EE00-1EEFF; -} -/* symbols */ -@font-face { - font-family: 'Open Sans'; - font-style: italic; - font-weight: 400; - font-stretch: 100%; - font-display: swap; - src: url(memtYaGs126MiZpBA-UFUIcVXSCEkx2cmqvXlWqW106F15M.woff2) format('woff2'); - unicode-range: U+0001-000C, U+000E-001F, U+007F-009F, U+20DD-20E0, U+20E2-20E4, U+2150-218F, U+2190, U+2192, U+2194-2199, U+21AF, U+21E6-21F0, U+21F3, U+2218-2219, U+2299, U+22C4-22C6, U+2300-243F, U+2440-244A, U+2460-24FF, U+25A0-27BF, U+2800-28FF, U+2921-2922, U+2981, U+29BF, U+29EB, U+2B00-2BFF, U+4DC0-4DFF, U+FFF9-FFFB, U+10140-1018E, U+10190-1019C, U+101A0, U+101D0-101FD, U+102E0-102FB, U+10E60-10E7E, U+1D2C0-1D2D3, U+1D2E0-1D37F, U+1F000-1F0FF, U+1F100-1F1AD, U+1F1E6-1F1FF, U+1F30D-1F30F, U+1F315, U+1F31C, U+1F31E, U+1F320-1F32C, U+1F336, U+1F378, U+1F37D, U+1F382, U+1F393-1F39F, U+1F3A7-1F3A8, U+1F3AC-1F3AF, U+1F3C2, U+1F3C4-1F3C6, U+1F3CA-1F3CE, U+1F3D4-1F3E0, U+1F3ED, U+1F3F1-1F3F3, U+1F3F5-1F3F7, U+1F408, U+1F415, U+1F41F, U+1F426, U+1F43F, U+1F441-1F442, U+1F444, U+1F446-1F449, U+1F44C-1F44E, U+1F453, U+1F46A, U+1F47D, U+1F4A3, U+1F4B0, U+1F4B3, U+1F4B9, U+1F4BB, U+1F4BF, U+1F4C8-1F4CB, U+1F4D6, U+1F4DA, U+1F4DF, U+1F4E3-1F4E6, U+1F4EA-1F4ED, U+1F4F7, U+1F4F9-1F4FB, U+1F4FD-1F4FE, U+1F503, U+1F507-1F50B, U+1F50D, U+1F512-1F513, U+1F53E-1F54A, U+1F54F-1F5FA, U+1F610, U+1F650-1F67F, U+1F687, U+1F68D, U+1F691, U+1F694, U+1F698, U+1F6AD, U+1F6B2, U+1F6B9-1F6BA, U+1F6BC, U+1F6C6-1F6CF, U+1F6D3-1F6D7, U+1F6E0-1F6EA, U+1F6F0-1F6F3, U+1F6F7-1F6FC, U+1F700-1F7FF, U+1F800-1F80B, U+1F810-1F847, U+1F850-1F859, U+1F860-1F887, U+1F890-1F8AD, U+1F8B0-1F8BB, U+1F8C0-1F8C1, U+1F900-1F90B, U+1F93B, U+1F946, U+1F984, U+1F996, U+1F9E9, U+1FA00-1FA6F, U+1FA70-1FA7C, U+1FA80-1FA89, U+1FA8F-1FAC6, U+1FACE-1FADC, U+1FADF-1FAE9, U+1FAF0-1FAF8, U+1FB00-1FBFF; -} -/* vietnamese */ -@font-face { - font-family: 'Open Sans'; - font-style: italic; - font-weight: 400; - font-stretch: 100%; - font-display: swap; - src: url(memtYaGs126MiZpBA-UFUIcVXSCEkx2cmqvXlWqWtk6F15M.woff2) format('woff2'); - unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+0300-0301, U+0303-0304, U+0308-0309, U+0323, U+0329, U+1EA0-1EF9, U+20AB; -} -/* latin-ext */ -@font-face { - font-family: 'Open Sans'; - font-style: italic; - font-weight: 400; - font-stretch: 100%; - font-display: swap; - src: url(memtYaGs126MiZpBA-UFUIcVXSCEkx2cmqvXlWqWt06F15M.woff2) format('woff2'); - unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF; -} -/* latin */ -@font-face { - font-family: 'Open Sans'; - font-style: italic; - font-weight: 400; - font-stretch: 100%; - font-display: swap; - src: url(memtYaGs126MiZpBA-UFUIcVXSCEkx2cmqvXlWqWuU6F.woff2) format('woff2'); - unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD; -} -/* cyrillic-ext */ -@font-face { - font-family: 'Open Sans'; - font-style: italic; - font-weight: 600; - font-stretch: 100%; - font-display: swap; - src: url(memtYaGs126MiZpBA-UFUIcVXSCEkx2cmqvXlWqWtE6F15M.woff2) format('woff2'); - unicode-range: U+0460-052F, U+1C80-1C8A, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F; -} -/* cyrillic */ -@font-face { - font-family: 'Open Sans'; - font-style: italic; - font-weight: 600; - font-stretch: 100%; - font-display: swap; - src: url(memtYaGs126MiZpBA-UFUIcVXSCEkx2cmqvXlWqWvU6F15M.woff2) format('woff2'); - unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116; -} -/* greek-ext */ -@font-face { - font-family: 'Open Sans'; - font-style: italic; - font-weight: 600; - font-stretch: 100%; - font-display: swap; - src: url(memtYaGs126MiZpBA-UFUIcVXSCEkx2cmqvXlWqWtU6F15M.woff2) format('woff2'); - unicode-range: U+1F00-1FFF; -} -/* greek */ -@font-face { - font-family: 'Open Sans'; - font-style: italic; - font-weight: 600; - font-stretch: 100%; - font-display: swap; - src: url(memtYaGs126MiZpBA-UFUIcVXSCEkx2cmqvXlWqWuk6F15M.woff2) format('woff2'); - unicode-range: U+0370-0377, U+037A-037F, U+0384-038A, U+038C, U+038E-03A1, U+03A3-03FF; -} -/* hebrew */ -@font-face { - font-family: 'Open Sans'; - font-style: italic; - font-weight: 600; - font-stretch: 100%; - font-display: swap; - src: url(memtYaGs126MiZpBA-UFUIcVXSCEkx2cmqvXlWqWu06F15M.woff2) format('woff2'); - unicode-range: U+0307-0308, U+0590-05FF, U+200C-2010, U+20AA, U+25CC, U+FB1D-FB4F; -} -/* math */ -@font-face { - font-family: 'Open Sans'; - font-style: italic; - font-weight: 600; - font-stretch: 100%; - font-display: swap; - src: url(memtYaGs126MiZpBA-UFUIcVXSCEkx2cmqvXlWqWxU6F15M.woff2) format('woff2'); - unicode-range: U+0302-0303, U+0305, U+0307-0308, U+0310, U+0312, U+0315, U+031A, U+0326-0327, U+032C, U+032F-0330, U+0332-0333, U+0338, U+033A, U+0346, U+034D, U+0391-03A1, U+03A3-03A9, U+03B1-03C9, U+03D1, U+03D5-03D6, U+03F0-03F1, U+03F4-03F5, U+2016-2017, U+2034-2038, U+203C, U+2040, U+2043, U+2047, U+2050, U+2057, U+205F, U+2070-2071, U+2074-208E, U+2090-209C, U+20D0-20DC, U+20E1, U+20E5-20EF, U+2100-2112, U+2114-2115, U+2117-2121, U+2123-214F, U+2190, U+2192, U+2194-21AE, U+21B0-21E5, U+21F1-21F2, U+21F4-2211, U+2213-2214, U+2216-22FF, U+2308-230B, U+2310, U+2319, U+231C-2321, U+2336-237A, U+237C, U+2395, U+239B-23B7, U+23D0, U+23DC-23E1, U+2474-2475, U+25AF, U+25B3, U+25B7, U+25BD, U+25C1, U+25CA, U+25CC, U+25FB, U+266D-266F, U+27C0-27FF, U+2900-2AFF, U+2B0E-2B11, U+2B30-2B4C, U+2BFE, U+3030, U+FF5B, U+FF5D, U+1D400-1D7FF, U+1EE00-1EEFF; -} -/* symbols */ -@font-face { - font-family: 'Open Sans'; - font-style: italic; - font-weight: 600; - font-stretch: 100%; - font-display: swap; - src: url(memtYaGs126MiZpBA-UFUIcVXSCEkx2cmqvXlWqW106F15M.woff2) format('woff2'); - unicode-range: U+0001-000C, U+000E-001F, U+007F-009F, U+20DD-20E0, U+20E2-20E4, U+2150-218F, U+2190, U+2192, U+2194-2199, U+21AF, U+21E6-21F0, U+21F3, U+2218-2219, U+2299, U+22C4-22C6, U+2300-243F, U+2440-244A, U+2460-24FF, U+25A0-27BF, U+2800-28FF, U+2921-2922, U+2981, U+29BF, U+29EB, U+2B00-2BFF, U+4DC0-4DFF, U+FFF9-FFFB, U+10140-1018E, U+10190-1019C, U+101A0, U+101D0-101FD, U+102E0-102FB, U+10E60-10E7E, U+1D2C0-1D2D3, U+1D2E0-1D37F, U+1F000-1F0FF, U+1F100-1F1AD, U+1F1E6-1F1FF, U+1F30D-1F30F, U+1F315, U+1F31C, U+1F31E, U+1F320-1F32C, U+1F336, U+1F378, U+1F37D, U+1F382, U+1F393-1F39F, U+1F3A7-1F3A8, U+1F3AC-1F3AF, U+1F3C2, U+1F3C4-1F3C6, U+1F3CA-1F3CE, U+1F3D4-1F3E0, U+1F3ED, U+1F3F1-1F3F3, U+1F3F5-1F3F7, U+1F408, U+1F415, U+1F41F, U+1F426, U+1F43F, U+1F441-1F442, U+1F444, U+1F446-1F449, U+1F44C-1F44E, U+1F453, U+1F46A, U+1F47D, U+1F4A3, U+1F4B0, U+1F4B3, U+1F4B9, U+1F4BB, U+1F4BF, U+1F4C8-1F4CB, U+1F4D6, U+1F4DA, U+1F4DF, U+1F4E3-1F4E6, U+1F4EA-1F4ED, U+1F4F7, U+1F4F9-1F4FB, U+1F4FD-1F4FE, U+1F503, U+1F507-1F50B, U+1F50D, U+1F512-1F513, U+1F53E-1F54A, U+1F54F-1F5FA, U+1F610, U+1F650-1F67F, U+1F687, U+1F68D, U+1F691, U+1F694, U+1F698, U+1F6AD, U+1F6B2, U+1F6B9-1F6BA, U+1F6BC, U+1F6C6-1F6CF, U+1F6D3-1F6D7, U+1F6E0-1F6EA, U+1F6F0-1F6F3, U+1F6F7-1F6FC, U+1F700-1F7FF, U+1F800-1F80B, U+1F810-1F847, U+1F850-1F859, U+1F860-1F887, U+1F890-1F8AD, U+1F8B0-1F8BB, U+1F8C0-1F8C1, U+1F900-1F90B, U+1F93B, U+1F946, U+1F984, U+1F996, U+1F9E9, U+1FA00-1FA6F, U+1FA70-1FA7C, U+1FA80-1FA89, U+1FA8F-1FAC6, U+1FACE-1FADC, U+1FADF-1FAE9, U+1FAF0-1FAF8, U+1FB00-1FBFF; -} -/* vietnamese */ -@font-face { - font-family: 'Open Sans'; - font-style: italic; - font-weight: 600; - font-stretch: 100%; - font-display: swap; - src: url(memtYaGs126MiZpBA-UFUIcVXSCEkx2cmqvXlWqWtk6F15M.woff2) format('woff2'); - unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+0300-0301, U+0303-0304, U+0308-0309, U+0323, U+0329, U+1EA0-1EF9, U+20AB; -} -/* latin-ext */ -@font-face { - font-family: 'Open Sans'; - font-style: italic; - font-weight: 600; - font-stretch: 100%; - font-display: swap; - src: url(memtYaGs126MiZpBA-UFUIcVXSCEkx2cmqvXlWqWt06F15M.woff2) format('woff2'); - unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF; -} -/* latin */ -@font-face { - font-family: 'Open Sans'; - font-style: italic; - font-weight: 600; - font-stretch: 100%; - font-display: swap; - src: url(memtYaGs126MiZpBA-UFUIcVXSCEkx2cmqvXlWqWuU6F.woff2) format('woff2'); - unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD; -} -/* cyrillic-ext */ -@font-face { - font-family: 'Open Sans'; - font-style: normal; - font-weight: 300; - font-stretch: 100%; - font-display: swap; - src: url(memvYaGs126MiZpBA-UvWbX2vVnXBbObj2OVTSKmu1aB.woff2) format('woff2'); - unicode-range: U+0460-052F, U+1C80-1C8A, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F; -} -/* cyrillic */ -@font-face { - font-family: 'Open Sans'; - font-style: normal; - font-weight: 300; - font-stretch: 100%; - font-display: swap; - src: url(memvYaGs126MiZpBA-UvWbX2vVnXBbObj2OVTSumu1aB.woff2) format('woff2'); - unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116; -} -/* greek-ext */ -@font-face { - font-family: 'Open Sans'; - font-style: normal; - font-weight: 300; - font-stretch: 100%; - font-display: swap; - src: url(memvYaGs126MiZpBA-UvWbX2vVnXBbObj2OVTSOmu1aB.woff2) format('woff2'); - unicode-range: U+1F00-1FFF; -} -/* greek */ -@font-face { - font-family: 'Open Sans'; - font-style: normal; - font-weight: 300; - font-stretch: 100%; - font-display: swap; - src: url(memvYaGs126MiZpBA-UvWbX2vVnXBbObj2OVTSymu1aB.woff2) format('woff2'); - unicode-range: U+0370-0377, U+037A-037F, U+0384-038A, U+038C, U+038E-03A1, U+03A3-03FF; -} -/* hebrew */ -@font-face { - font-family: 'Open Sans'; - font-style: normal; - font-weight: 300; - font-stretch: 100%; - font-display: swap; - src: url(memvYaGs126MiZpBA-UvWbX2vVnXBbObj2OVTS2mu1aB.woff2) format('woff2'); - unicode-range: U+0307-0308, U+0590-05FF, U+200C-2010, U+20AA, U+25CC, U+FB1D-FB4F; -} -/* math */ -@font-face { - font-family: 'Open Sans'; - font-style: normal; - font-weight: 300; - font-stretch: 100%; - font-display: swap; - src: url(memvYaGs126MiZpBA-UvWbX2vVnXBbObj2OVTVOmu1aB.woff2) format('woff2'); - unicode-range: U+0302-0303, U+0305, U+0307-0308, U+0310, U+0312, U+0315, U+031A, U+0326-0327, U+032C, U+032F-0330, U+0332-0333, U+0338, U+033A, U+0346, U+034D, U+0391-03A1, U+03A3-03A9, U+03B1-03C9, U+03D1, U+03D5-03D6, U+03F0-03F1, U+03F4-03F5, U+2016-2017, U+2034-2038, U+203C, U+2040, U+2043, U+2047, U+2050, U+2057, U+205F, U+2070-2071, U+2074-208E, U+2090-209C, U+20D0-20DC, U+20E1, U+20E5-20EF, U+2100-2112, U+2114-2115, U+2117-2121, U+2123-214F, U+2190, U+2192, U+2194-21AE, U+21B0-21E5, U+21F1-21F2, U+21F4-2211, U+2213-2214, U+2216-22FF, U+2308-230B, U+2310, U+2319, U+231C-2321, U+2336-237A, U+237C, U+2395, U+239B-23B7, U+23D0, U+23DC-23E1, U+2474-2475, U+25AF, U+25B3, U+25B7, U+25BD, U+25C1, U+25CA, U+25CC, U+25FB, U+266D-266F, U+27C0-27FF, U+2900-2AFF, U+2B0E-2B11, U+2B30-2B4C, U+2BFE, U+3030, U+FF5B, U+FF5D, U+1D400-1D7FF, U+1EE00-1EEFF; -} -/* symbols */ -@font-face { - font-family: 'Open Sans'; - font-style: normal; - font-weight: 300; - font-stretch: 100%; - font-display: swap; - src: url(memvYaGs126MiZpBA-UvWbX2vVnXBbObj2OVTUGmu1aB.woff2) format('woff2'); - unicode-range: U+0001-000C, U+000E-001F, U+007F-009F, U+20DD-20E0, U+20E2-20E4, U+2150-218F, U+2190, U+2192, U+2194-2199, U+21AF, U+21E6-21F0, U+21F3, U+2218-2219, U+2299, U+22C4-22C6, U+2300-243F, U+2440-244A, U+2460-24FF, U+25A0-27BF, U+2800-28FF, U+2921-2922, U+2981, U+29BF, U+29EB, U+2B00-2BFF, U+4DC0-4DFF, U+FFF9-FFFB, U+10140-1018E, U+10190-1019C, U+101A0, U+101D0-101FD, U+102E0-102FB, U+10E60-10E7E, U+1D2C0-1D2D3, U+1D2E0-1D37F, U+1F000-1F0FF, U+1F100-1F1AD, U+1F1E6-1F1FF, U+1F30D-1F30F, U+1F315, U+1F31C, U+1F31E, U+1F320-1F32C, U+1F336, U+1F378, U+1F37D, U+1F382, U+1F393-1F39F, U+1F3A7-1F3A8, U+1F3AC-1F3AF, U+1F3C2, U+1F3C4-1F3C6, U+1F3CA-1F3CE, U+1F3D4-1F3E0, U+1F3ED, U+1F3F1-1F3F3, U+1F3F5-1F3F7, U+1F408, U+1F415, U+1F41F, U+1F426, U+1F43F, U+1F441-1F442, U+1F444, U+1F446-1F449, U+1F44C-1F44E, U+1F453, U+1F46A, U+1F47D, U+1F4A3, U+1F4B0, U+1F4B3, U+1F4B9, U+1F4BB, U+1F4BF, U+1F4C8-1F4CB, U+1F4D6, U+1F4DA, U+1F4DF, U+1F4E3-1F4E6, U+1F4EA-1F4ED, U+1F4F7, U+1F4F9-1F4FB, U+1F4FD-1F4FE, U+1F503, U+1F507-1F50B, U+1F50D, U+1F512-1F513, U+1F53E-1F54A, U+1F54F-1F5FA, U+1F610, U+1F650-1F67F, U+1F687, U+1F68D, U+1F691, U+1F694, U+1F698, U+1F6AD, U+1F6B2, U+1F6B9-1F6BA, U+1F6BC, U+1F6C6-1F6CF, U+1F6D3-1F6D7, U+1F6E0-1F6EA, U+1F6F0-1F6F3, U+1F6F7-1F6FC, U+1F700-1F7FF, U+1F800-1F80B, U+1F810-1F847, U+1F850-1F859, U+1F860-1F887, U+1F890-1F8AD, U+1F8B0-1F8BB, U+1F8C0-1F8C1, U+1F900-1F90B, U+1F93B, U+1F946, U+1F984, U+1F996, U+1F9E9, U+1FA00-1FA6F, U+1FA70-1FA7C, U+1FA80-1FA89, U+1FA8F-1FAC6, U+1FACE-1FADC, U+1FADF-1FAE9, U+1FAF0-1FAF8, U+1FB00-1FBFF; -} -/* vietnamese */ -@font-face { - font-family: 'Open Sans'; - font-style: normal; - font-weight: 300; - font-stretch: 100%; - font-display: swap; - src: url(memvYaGs126MiZpBA-UvWbX2vVnXBbObj2OVTSCmu1aB.woff2) format('woff2'); - unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+0300-0301, U+0303-0304, U+0308-0309, U+0323, U+0329, U+1EA0-1EF9, U+20AB; -} -/* latin-ext */ -@font-face { - font-family: 'Open Sans'; - font-style: normal; - font-weight: 300; - font-stretch: 100%; - font-display: swap; - src: url(memvYaGs126MiZpBA-UvWbX2vVnXBbObj2OVTSGmu1aB.woff2) format('woff2'); - unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF; -} -/* latin */ -@font-face { - font-family: 'Open Sans'; - font-style: normal; - font-weight: 300; - font-stretch: 100%; - font-display: swap; - src: url(memvYaGs126MiZpBA-UvWbX2vVnXBbObj2OVTS-muw.woff2) format('woff2'); - unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD; -} -/* cyrillic-ext */ -@font-face { - font-family: 'Open Sans'; - font-style: normal; - font-weight: 400; - font-stretch: 100%; - font-display: swap; - src: url(memvYaGs126MiZpBA-UvWbX2vVnXBbObj2OVTSKmu1aB.woff2) format('woff2'); - unicode-range: U+0460-052F, U+1C80-1C8A, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F; -} -/* cyrillic */ -@font-face { - font-family: 'Open Sans'; - font-style: normal; - font-weight: 400; - font-stretch: 100%; - font-display: swap; - src: url(memvYaGs126MiZpBA-UvWbX2vVnXBbObj2OVTSumu1aB.woff2) format('woff2'); - unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116; -} -/* greek-ext */ -@font-face { - font-family: 'Open Sans'; - font-style: normal; - font-weight: 400; - font-stretch: 100%; - font-display: swap; - src: url(memvYaGs126MiZpBA-UvWbX2vVnXBbObj2OVTSOmu1aB.woff2) format('woff2'); - unicode-range: U+1F00-1FFF; -} -/* greek */ -@font-face { - font-family: 'Open Sans'; - font-style: normal; - font-weight: 400; - font-stretch: 100%; - font-display: swap; - src: url(memvYaGs126MiZpBA-UvWbX2vVnXBbObj2OVTSymu1aB.woff2) format('woff2'); - unicode-range: U+0370-0377, U+037A-037F, U+0384-038A, U+038C, U+038E-03A1, U+03A3-03FF; -} -/* hebrew */ -@font-face { - font-family: 'Open Sans'; - font-style: normal; - font-weight: 400; - font-stretch: 100%; - font-display: swap; - src: url(memvYaGs126MiZpBA-UvWbX2vVnXBbObj2OVTS2mu1aB.woff2) format('woff2'); - unicode-range: U+0307-0308, U+0590-05FF, U+200C-2010, U+20AA, U+25CC, U+FB1D-FB4F; -} -/* math */ -@font-face { - font-family: 'Open Sans'; - font-style: normal; - font-weight: 400; - font-stretch: 100%; - font-display: swap; - src: url(memvYaGs126MiZpBA-UvWbX2vVnXBbObj2OVTVOmu1aB.woff2) format('woff2'); - unicode-range: U+0302-0303, U+0305, U+0307-0308, U+0310, U+0312, U+0315, U+031A, U+0326-0327, U+032C, U+032F-0330, U+0332-0333, U+0338, U+033A, U+0346, U+034D, U+0391-03A1, U+03A3-03A9, U+03B1-03C9, U+03D1, U+03D5-03D6, U+03F0-03F1, U+03F4-03F5, U+2016-2017, U+2034-2038, U+203C, U+2040, U+2043, U+2047, U+2050, U+2057, U+205F, U+2070-2071, U+2074-208E, U+2090-209C, U+20D0-20DC, U+20E1, U+20E5-20EF, U+2100-2112, U+2114-2115, U+2117-2121, U+2123-214F, U+2190, U+2192, U+2194-21AE, U+21B0-21E5, U+21F1-21F2, U+21F4-2211, U+2213-2214, U+2216-22FF, U+2308-230B, U+2310, U+2319, U+231C-2321, U+2336-237A, U+237C, U+2395, U+239B-23B7, U+23D0, U+23DC-23E1, U+2474-2475, U+25AF, U+25B3, U+25B7, U+25BD, U+25C1, U+25CA, U+25CC, U+25FB, U+266D-266F, U+27C0-27FF, U+2900-2AFF, U+2B0E-2B11, U+2B30-2B4C, U+2BFE, U+3030, U+FF5B, U+FF5D, U+1D400-1D7FF, U+1EE00-1EEFF; -} -/* symbols */ -@font-face { - font-family: 'Open Sans'; - font-style: normal; - font-weight: 400; - font-stretch: 100%; - font-display: swap; - src: url(memvYaGs126MiZpBA-UvWbX2vVnXBbObj2OVTUGmu1aB.woff2) format('woff2'); - unicode-range: U+0001-000C, U+000E-001F, U+007F-009F, U+20DD-20E0, U+20E2-20E4, U+2150-218F, U+2190, U+2192, U+2194-2199, U+21AF, U+21E6-21F0, U+21F3, U+2218-2219, U+2299, U+22C4-22C6, U+2300-243F, U+2440-244A, U+2460-24FF, U+25A0-27BF, U+2800-28FF, U+2921-2922, U+2981, U+29BF, U+29EB, U+2B00-2BFF, U+4DC0-4DFF, U+FFF9-FFFB, U+10140-1018E, U+10190-1019C, U+101A0, U+101D0-101FD, U+102E0-102FB, U+10E60-10E7E, U+1D2C0-1D2D3, U+1D2E0-1D37F, U+1F000-1F0FF, U+1F100-1F1AD, U+1F1E6-1F1FF, U+1F30D-1F30F, U+1F315, U+1F31C, U+1F31E, U+1F320-1F32C, U+1F336, U+1F378, U+1F37D, U+1F382, U+1F393-1F39F, U+1F3A7-1F3A8, U+1F3AC-1F3AF, U+1F3C2, U+1F3C4-1F3C6, U+1F3CA-1F3CE, U+1F3D4-1F3E0, U+1F3ED, U+1F3F1-1F3F3, U+1F3F5-1F3F7, U+1F408, U+1F415, U+1F41F, U+1F426, U+1F43F, U+1F441-1F442, U+1F444, U+1F446-1F449, U+1F44C-1F44E, U+1F453, U+1F46A, U+1F47D, U+1F4A3, U+1F4B0, U+1F4B3, U+1F4B9, U+1F4BB, U+1F4BF, U+1F4C8-1F4CB, U+1F4D6, U+1F4DA, U+1F4DF, U+1F4E3-1F4E6, U+1F4EA-1F4ED, U+1F4F7, U+1F4F9-1F4FB, U+1F4FD-1F4FE, U+1F503, U+1F507-1F50B, U+1F50D, U+1F512-1F513, U+1F53E-1F54A, U+1F54F-1F5FA, U+1F610, U+1F650-1F67F, U+1F687, U+1F68D, U+1F691, U+1F694, U+1F698, U+1F6AD, U+1F6B2, U+1F6B9-1F6BA, U+1F6BC, U+1F6C6-1F6CF, U+1F6D3-1F6D7, U+1F6E0-1F6EA, U+1F6F0-1F6F3, U+1F6F7-1F6FC, U+1F700-1F7FF, U+1F800-1F80B, U+1F810-1F847, U+1F850-1F859, U+1F860-1F887, U+1F890-1F8AD, U+1F8B0-1F8BB, U+1F8C0-1F8C1, U+1F900-1F90B, U+1F93B, U+1F946, U+1F984, U+1F996, U+1F9E9, U+1FA00-1FA6F, U+1FA70-1FA7C, U+1FA80-1FA89, U+1FA8F-1FAC6, U+1FACE-1FADC, U+1FADF-1FAE9, U+1FAF0-1FAF8, U+1FB00-1FBFF; -} -/* vietnamese */ -@font-face { - font-family: 'Open Sans'; - font-style: normal; - font-weight: 400; - font-stretch: 100%; - font-display: swap; - src: url(memvYaGs126MiZpBA-UvWbX2vVnXBbObj2OVTSCmu1aB.woff2) format('woff2'); - unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+0300-0301, U+0303-0304, U+0308-0309, U+0323, U+0329, U+1EA0-1EF9, U+20AB; -} -/* latin-ext */ -@font-face { - font-family: 'Open Sans'; - font-style: normal; - font-weight: 400; - font-stretch: 100%; - font-display: swap; - src: url(memvYaGs126MiZpBA-UvWbX2vVnXBbObj2OVTSGmu1aB.woff2) format('woff2'); - unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF; -} -/* latin */ -@font-face { - font-family: 'Open Sans'; - font-style: normal; - font-weight: 400; - font-stretch: 100%; - font-display: swap; - src: url(memvYaGs126MiZpBA-UvWbX2vVnXBbObj2OVTS-muw.woff2) format('woff2'); - unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD; -} -/* cyrillic-ext */ -@font-face { - font-family: 'Open Sans'; - font-style: normal; - font-weight: 600; - font-stretch: 100%; - font-display: swap; - src: url(memvYaGs126MiZpBA-UvWbX2vVnXBbObj2OVTSKmu1aB.woff2) format('woff2'); - unicode-range: U+0460-052F, U+1C80-1C8A, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F; -} -/* cyrillic */ -@font-face { - font-family: 'Open Sans'; - font-style: normal; - font-weight: 600; - font-stretch: 100%; - font-display: swap; - src: url(memvYaGs126MiZpBA-UvWbX2vVnXBbObj2OVTSumu1aB.woff2) format('woff2'); - unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116; -} -/* greek-ext */ -@font-face { - font-family: 'Open Sans'; - font-style: normal; - font-weight: 600; - font-stretch: 100%; - font-display: swap; - src: url(memvYaGs126MiZpBA-UvWbX2vVnXBbObj2OVTSOmu1aB.woff2) format('woff2'); - unicode-range: U+1F00-1FFF; -} -/* greek */ -@font-face { - font-family: 'Open Sans'; - font-style: normal; - font-weight: 600; - font-stretch: 100%; - font-display: swap; - src: url(memvYaGs126MiZpBA-UvWbX2vVnXBbObj2OVTSymu1aB.woff2) format('woff2'); - unicode-range: U+0370-0377, U+037A-037F, U+0384-038A, U+038C, U+038E-03A1, U+03A3-03FF; -} -/* hebrew */ -@font-face { - font-family: 'Open Sans'; - font-style: normal; - font-weight: 600; - font-stretch: 100%; - font-display: swap; - src: url(memvYaGs126MiZpBA-UvWbX2vVnXBbObj2OVTS2mu1aB.woff2) format('woff2'); - unicode-range: U+0307-0308, U+0590-05FF, U+200C-2010, U+20AA, U+25CC, U+FB1D-FB4F; -} -/* math */ -@font-face { - font-family: 'Open Sans'; - font-style: normal; - font-weight: 600; - font-stretch: 100%; - font-display: swap; - src: url(memvYaGs126MiZpBA-UvWbX2vVnXBbObj2OVTVOmu1aB.woff2) format('woff2'); - unicode-range: U+0302-0303, U+0305, U+0307-0308, U+0310, U+0312, U+0315, U+031A, U+0326-0327, U+032C, U+032F-0330, U+0332-0333, U+0338, U+033A, U+0346, U+034D, U+0391-03A1, U+03A3-03A9, U+03B1-03C9, U+03D1, U+03D5-03D6, U+03F0-03F1, U+03F4-03F5, U+2016-2017, U+2034-2038, U+203C, U+2040, U+2043, U+2047, U+2050, U+2057, U+205F, U+2070-2071, U+2074-208E, U+2090-209C, U+20D0-20DC, U+20E1, U+20E5-20EF, U+2100-2112, U+2114-2115, U+2117-2121, U+2123-214F, U+2190, U+2192, U+2194-21AE, U+21B0-21E5, U+21F1-21F2, U+21F4-2211, U+2213-2214, U+2216-22FF, U+2308-230B, U+2310, U+2319, U+231C-2321, U+2336-237A, U+237C, U+2395, U+239B-23B7, U+23D0, U+23DC-23E1, U+2474-2475, U+25AF, U+25B3, U+25B7, U+25BD, U+25C1, U+25CA, U+25CC, U+25FB, U+266D-266F, U+27C0-27FF, U+2900-2AFF, U+2B0E-2B11, U+2B30-2B4C, U+2BFE, U+3030, U+FF5B, U+FF5D, U+1D400-1D7FF, U+1EE00-1EEFF; -} -/* symbols */ -@font-face { - font-family: 'Open Sans'; - font-style: normal; - font-weight: 600; - font-stretch: 100%; - font-display: swap; - src: url(memvYaGs126MiZpBA-UvWbX2vVnXBbObj2OVTUGmu1aB.woff2) format('woff2'); - unicode-range: U+0001-000C, U+000E-001F, U+007F-009F, U+20DD-20E0, U+20E2-20E4, U+2150-218F, U+2190, U+2192, U+2194-2199, U+21AF, U+21E6-21F0, U+21F3, U+2218-2219, U+2299, U+22C4-22C6, U+2300-243F, U+2440-244A, U+2460-24FF, U+25A0-27BF, U+2800-28FF, U+2921-2922, U+2981, U+29BF, U+29EB, U+2B00-2BFF, U+4DC0-4DFF, U+FFF9-FFFB, U+10140-1018E, U+10190-1019C, U+101A0, U+101D0-101FD, U+102E0-102FB, U+10E60-10E7E, U+1D2C0-1D2D3, U+1D2E0-1D37F, U+1F000-1F0FF, U+1F100-1F1AD, U+1F1E6-1F1FF, U+1F30D-1F30F, U+1F315, U+1F31C, U+1F31E, U+1F320-1F32C, U+1F336, U+1F378, U+1F37D, U+1F382, U+1F393-1F39F, U+1F3A7-1F3A8, U+1F3AC-1F3AF, U+1F3C2, U+1F3C4-1F3C6, U+1F3CA-1F3CE, U+1F3D4-1F3E0, U+1F3ED, U+1F3F1-1F3F3, U+1F3F5-1F3F7, U+1F408, U+1F415, U+1F41F, U+1F426, U+1F43F, U+1F441-1F442, U+1F444, U+1F446-1F449, U+1F44C-1F44E, U+1F453, U+1F46A, U+1F47D, U+1F4A3, U+1F4B0, U+1F4B3, U+1F4B9, U+1F4BB, U+1F4BF, U+1F4C8-1F4CB, U+1F4D6, U+1F4DA, U+1F4DF, U+1F4E3-1F4E6, U+1F4EA-1F4ED, U+1F4F7, U+1F4F9-1F4FB, U+1F4FD-1F4FE, U+1F503, U+1F507-1F50B, U+1F50D, U+1F512-1F513, U+1F53E-1F54A, U+1F54F-1F5FA, U+1F610, U+1F650-1F67F, U+1F687, U+1F68D, U+1F691, U+1F694, U+1F698, U+1F6AD, U+1F6B2, U+1F6B9-1F6BA, U+1F6BC, U+1F6C6-1F6CF, U+1F6D3-1F6D7, U+1F6E0-1F6EA, U+1F6F0-1F6F3, U+1F6F7-1F6FC, U+1F700-1F7FF, U+1F800-1F80B, U+1F810-1F847, U+1F850-1F859, U+1F860-1F887, U+1F890-1F8AD, U+1F8B0-1F8BB, U+1F8C0-1F8C1, U+1F900-1F90B, U+1F93B, U+1F946, U+1F984, U+1F996, U+1F9E9, U+1FA00-1FA6F, U+1FA70-1FA7C, U+1FA80-1FA89, U+1FA8F-1FAC6, U+1FACE-1FADC, U+1FADF-1FAE9, U+1FAF0-1FAF8, U+1FB00-1FBFF; -} -/* vietnamese */ -@font-face { - font-family: 'Open Sans'; - font-style: normal; - font-weight: 600; - font-stretch: 100%; - font-display: swap; - src: url(memvYaGs126MiZpBA-UvWbX2vVnXBbObj2OVTSCmu1aB.woff2) format('woff2'); - unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+0300-0301, U+0303-0304, U+0308-0309, U+0323, U+0329, U+1EA0-1EF9, U+20AB; -} -/* latin-ext */ -@font-face { - font-family: 'Open Sans'; - font-style: normal; - font-weight: 600; - font-stretch: 100%; - font-display: swap; - src: url(memvYaGs126MiZpBA-UvWbX2vVnXBbObj2OVTSGmu1aB.woff2) format('woff2'); - unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF; -} -/* latin */ -@font-face { - font-family: 'Open Sans'; - font-style: normal; - font-weight: 600; - font-stretch: 100%; - font-display: swap; - src: url(memvYaGs126MiZpBA-UvWbX2vVnXBbObj2OVTS-muw.woff2) format('woff2'); - unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD; -} diff --git a/src/blog/static/blog/js/mathjax-loader.js b/src/blog/static/blog/js/mathjax-loader.js deleted file mode 100644 index c922fc7..0000000 --- a/src/blog/static/blog/js/mathjax-loader.js +++ /dev/null @@ -1,142 +0,0 @@ -/** - * MathJax 智能加载器 - * 检测页面是否包含数学公式,如果有则动态加载和配置MathJax - */ -(function() { - 'use strict'; - - /** - * 检测页面是否包含数学公式 - * @returns {boolean} 是否包含数学公式 - */ - function hasMathFormulas() { - const content = document.body.textContent || document.body.innerText || ''; - // 检测常见的数学公式语法 - return /\$.*?\$|\$\$.*?\$\$|\\begin\{.*?\}|\\end\{.*?\}|\\[a-zA-Z]+\{/.test(content); - } - - /** - * 配置MathJax - */ - function configureMathJax() { - window.MathJax = { - tex: { - // 行内公式和块级公式分隔符 - inlineMath: [['$', '$']], - displayMath: [['$$', '$$']], - // 处理转义字符和LaTeX环境 - processEscapes: true, - processEnvironments: true, - // 自动换行 - tags: 'ams' - }, - options: { - // 跳过这些HTML标签,避免处理代码块等 - skipHtmlTags: ['script', 'noscript', 'style', 'textarea', 'pre', 'code', 'a'], - // CSS类控制 - ignoreHtmlClass: 'tex2jax_ignore', - processHtmlClass: 'tex2jax_process' - }, - // 启动配置 - startup: { - ready() { - console.log('MathJax配置完成,开始初始化...'); - MathJax.startup.defaultReady(); - - // 处理特定区域的数学公式 - const contentEl = document.getElementById('content'); - const commentsEl = document.getElementById('comments'); - - const promises = []; - if (contentEl) { - promises.push(MathJax.typesetPromise([contentEl])); - } - if (commentsEl) { - promises.push(MathJax.typesetPromise([commentsEl])); - } - - // 等待所有渲染完成 - Promise.all(promises).then(() => { - console.log('MathJax渲染完成'); - // 触发自定义事件,通知其他脚本MathJax已就绪 - document.dispatchEvent(new CustomEvent('mathjaxReady')); - }).catch(error => { - console.error('MathJax渲染失败:', error); - }); - } - }, - // 输出配置 - chtml: { - scale: 1, - minScale: 0.5, - matchFontHeight: false, - displayAlign: 'center', - displayIndent: '0' - } - }; - } - - /** - * 加载MathJax库 - */ - function loadMathJax() { - console.log('检测到数学公式,开始加载MathJax...'); - - const script = document.createElement('script'); - script.src = 'https://cdn.jsdelivr.net/npm/mathjax@3/es5/tex-mml-chtml.js'; - script.async = true; - script.defer = true; - - script.onload = function() { - console.log('MathJax库加载成功'); - }; - - script.onerror = function() { - console.error('MathJax库加载失败,尝试备用CDN...'); - // 备用CDN - const fallbackScript = document.createElement('script'); - fallbackScript.src = 'https://polyfill.io/v3/polyfill.min.js?features=es6'; - fallbackScript.onload = function() { - const mathJaxScript = document.createElement('script'); - mathJaxScript.src = 'https://cdn.mathjax.org/mathjax/latest/MathJax.js?config=TeX-MML-AM_CHTML'; - mathJaxScript.async = true; - document.head.appendChild(mathJaxScript); - }; - document.head.appendChild(fallbackScript); - }; - - document.head.appendChild(script); - } - - /** - * 初始化函数 - */ - function init() { - // 等待DOM完全加载 - if (document.readyState === 'loading') { - document.addEventListener('DOMContentLoaded', init); - return; - } - - // 检测是否需要加载MathJax - if (hasMathFormulas()) { - // 先配置,再加载 - configureMathJax(); - loadMathJax(); - } else { - console.log('未检测到数学公式,跳过MathJax加载'); - } - } - - // 提供重新渲染的全局方法,供动态内容使用 - window.rerenderMathJax = function(element) { - if (window.MathJax && window.MathJax.typesetPromise) { - const target = element || document.body; - return window.MathJax.typesetPromise([target]); - } - return Promise.resolve(); - }; - - // 启动初始化 - init(); -})(); diff --git a/src/codecov.yml b/src/codecov.yml deleted file mode 100644 index 2298829..0000000 --- a/src/codecov.yml +++ /dev/null @@ -1,87 +0,0 @@ -codecov: - require_ci_to_pass: yes - -coverage: - precision: 2 - round: down - range: "70...100" - - status: - project: - default: - target: auto - threshold: 1% - informational: true - patch: - default: - target: auto - threshold: 1% - informational: true - -parsers: - gcov: - branch_detection: - conditional: yes - loop: yes - method: no - macro: no - -comment: - layout: "reach,diff,flags,tree" - behavior: default - require_changes: no - -ignore: - # Django 相关 - - "*/migrations/*" - - "manage.py" - - "*/settings.py" - - "*/wsgi.py" - - "*/asgi.py" - - # 测试相关 - - "*/tests/*" - - "*/test_*.py" - - "*/*test*.py" - - # 静态文件和模板 - - "*/static/*" - - "*/templates/*" - - "*/collectedstatic/*" - - # 国际化文件 - - "*/locale/*" - - "**/*.po" - - "**/*.mo" - - # 文档和部署 - - "*/docs/*" - - "*/deploy/*" - - "README*.md" - - "LICENSE" - - "Dockerfile" - - "docker-compose*.yml" - - "*.yaml" - - "*.yml" - - # 开发环境 - - "*/venv/*" - - "*/__pycache__/*" - - "*.pyc" - - ".coverage" - - "coverage.xml" - - # 日志文件 - - "*/logs/*" - - "*.log" - - # 特定文件 - - "*/whoosh_cn_backend.py" # 搜索后端 - - "*/elasticsearch_backend.py" # 搜索后端 - - "*/MemcacheStorage.py" # 缓存存储 - - "*/robot.py" # 机器人相关 - - # 配置文件 - - "codecov.yml" - - ".coveragerc" - - "requirements*.txt" diff --git a/src/comments/admin.py b/src/comments/admin.py deleted file mode 100644 index 1ba7864..0000000 --- a/src/comments/admin.py +++ /dev/null @@ -1,76 +0,0 @@ -from django.contrib import admin -from django.urls import reverse -from django.utils.html import format_html -from django.utils.translation import gettext_lazy as _ - -# 定义一个动作函数,用于批量禁用评论状态 -def disable_commentstatus(modeladmin, request, queryset): - queryset.update(is_enable=False) # 将选中的评论的 `is_enable` 字段设置为 False - -# 定义一个动作函数,用于批量启用评论状态 -def enable_commentstatus(modeladmin, request, queryset): - queryset.update(is_enable=True) # 将选中的评论的 `is_enable` 字段设置为 True - -# 为动作函数设置描述信息,显示在 Django Admin 的操作下拉菜单中 -disable_commentstatus.short_description = _('Disable comments') -enable_commentstatus.short_description = _('Enable comments') - -# 定义评论模型的管理类 -class CommentAdmin(admin.ModelAdmin): - # 每页显示 20 条记录 - list_per_page = 20 - - # 定义在列表页面显示的字段 - list_display = ( - 'id', # 评论的 ID - 'body', # 评论内容 - 'link_to_userinfo', # 链接到用户信息 - 'link_to_article', # 链接到文章信息 - 'is_enable', # 评论是否启用 - 'creation_time' # 评论的创建时间 - ) - - # 定义哪些字段可以作为链接,点击后进入详情页面 - list_display_links = ('id', 'body', 'is_enable') - - # 定义过滤器,允许按 `is_enable` 字段筛选 - list_filter = ('is_enable',) - - # 定义在表单中排除的字段 - exclude = ('creation_time', 'last_modify_time') - - # 定义批量操作 - actions = [disable_commentstatus, enable_commentstatus] - - # 定义外键字段的快速选择功能 - raw_id_fields = ('author', 'article') - - # 定义搜索字段,支持通过 `body` 字段搜索评论 - search_fields = ('body',) - - # 定义一个方法,生成链接到用户信息的 HTML - def link_to_userinfo(self, obj): - # 获取用户模型的 app_label 和 model_name - info = (obj.author._meta.app_label, obj.author._meta.model_name) - # 生成用户详情页面的链接 - link = reverse('admin:%s_%s_change' % info, args=(obj.author.id,)) - # 返回 HTML 链接,显示用户昵称或邮箱 - return format_html( - u'%s' % - (link, obj.author.nickname if obj.author.nickname else obj.author.email) - ) - - # 定义一个方法,生成链接到文章信息的 HTML - def link_to_article(self, obj): - # 获取文章模型的 app_label 和 model_name - info = (obj.article._meta.app_label, obj.article._meta.model_name) - # 生成文章详情页面的链接 - link = reverse('admin:%s_%s_change' % info, args=(obj.article.id,)) - # 返回 HTML 链接,显示文章标题 - return format_html( - u'%s' % (link, obj.article.title) - ) - - # 设置方法的显示名称,用于 Django Admin 列表页面 - link_to_userinfo.short_description = _('User') - link_to_article.short_description = _('Article') \ No newline at end of file diff --git a/src/comments/views.py b/src/comments/views.py deleted file mode 100644 index 91786b4..0000000 --- a/src/comments/views.py +++ /dev/null @@ -1,91 +0,0 @@ -# 导入必要的模块和类 -from django.core.exceptions import ValidationError # 用于抛出验证错误 -from django.http import HttpResponseRedirect # 用于重定向 HTTP 请求 -from django.shortcuts import get_object_or_404 # 用于获取对象或返回 404 错误 -from django.utils.decorators import method_decorator # 用于装饰类方法 -from django.views.decorators.csrf import csrf_protect # 用于保护视图免受 CSRF 攻击 -from django.views.generic.edit import FormView # 通用视图,用于处理表单 - -from accounts.models import BlogUser # 导入用户模型 -from blog.models import Article # 导入文章模型 -from .forms import CommentForm # 导入评论表单 -from .models import Comment # 导入评论模型 - -# 定义一个基于表单的视图,用于处理评论提交 -class CommentPostView(FormView): - form_class = CommentForm # 指定使用的表单类 - template_name = 'blog/article_detail.html' # 指定模板文件 - - # 使用 CSRF 保护装饰器保护 dispatch 方法 - @method_decorator(csrf_protect) - def dispatch(self, *args, **kwargs): - # 调用父类的 dispatch 方法 - return super(CommentPostView, self).dispatch(*args, **kwargs) - - # 处理 GET 请求 - def get(self, request, *args, **kwargs): - # 获取文章 ID - article_id = self.kwargs['article_id'] - # 根据文章 ID 获取文章对象,如果不存在则返回 404 - article = get_object_or_404(Article, pk=article_id) - # 获取文章的绝对 URL - url = article.get_absolute_url() - # 重定向到文章详情页,并定位到评论部分 - return HttpResponseRedirect(url + "#comments") - - # 表单验证失败时的处理逻辑 - def form_invalid(self, form): - # 获取文章 ID - article_id = self.kwargs['article_id'] - # 根据文章 ID 获取文章对象 - article = get_object_or_404(Article, pk=article_id) - - # 返回包含表单和文章对象的响应 - return self.render_to_response({ - 'form': form, - 'article': article - }) - - # 表单验证成功时的处理逻辑 - def form_valid(self, form): - """提交的数据验证合法后的逻辑""" - # 获取当前登录用户 - user = self.request.user - # 根据用户 ID 获取用户对象 - author = BlogUser.objects.get(pk=user.pk) - # 获取文章 ID - article_id = self.kwargs['article_id'] - # 根据文章 ID 获取文章对象 - article = get_object_or_404(Article, pk=article_id) - - # 检查文章是否允许评论 - if article.comment_status == 'c' or article.status == 'c': - raise ValidationError("该文章评论已关闭.") # 抛出验证错误 - - # 创建评论对象,但不保存到数据库 - comment = form.save(False) - comment.article = article # 关联评论到文章 - - # 导入博客设置工具 - from djangoblog.utils import get_blog_setting - settings = get_blog_setting() # 获取博客设置 - - # 如果评论不需要审核,则直接启用评论 - if not settings.comment_need_review: - comment.is_enable = True - - comment.author = author # 设置评论的作者 - - # 如果存在父评论 ID,则关联父评论 - if form.cleaned_data['parent_comment_id']: - parent_comment = Comment.objects.get( - pk=form.cleaned_data['parent_comment_id']) - comment.parent_comment = parent_comment - - # 保存评论到数据库 - comment.save(True) - - # 重定向到文章详情页,并定位到新评论的位置 - return HttpResponseRedirect( - "%s#div-comment-%d" % - (article.get_absolute_url(), comment.pk)) \ No newline at end of file diff --git a/src/myenv/Include/site/python3.10/greenlet/greenlet.h b/src/myenv/Include/site/python3.10/greenlet/greenlet.h deleted file mode 100644 index d02a16e..0000000 --- a/src/myenv/Include/site/python3.10/greenlet/greenlet.h +++ /dev/null @@ -1,164 +0,0 @@ -/* -*- indent-tabs-mode: nil; tab-width: 4; -*- */ - -/* Greenlet object interface */ - -#ifndef Py_GREENLETOBJECT_H -#define Py_GREENLETOBJECT_H - - -#include - -#ifdef __cplusplus -extern "C" { -#endif - -/* This is deprecated and undocumented. It does not change. */ -#define GREENLET_VERSION "1.0.0" - -#ifndef GREENLET_MODULE -#define implementation_ptr_t void* -#endif - -typedef struct _greenlet { - PyObject_HEAD - PyObject* weakreflist; - PyObject* dict; - implementation_ptr_t pimpl; -} PyGreenlet; - -#define PyGreenlet_Check(op) (op && PyObject_TypeCheck(op, &PyGreenlet_Type)) - - -/* C API functions */ - -/* Total number of symbols that are exported */ -#define PyGreenlet_API_pointers 12 - -#define PyGreenlet_Type_NUM 0 -#define PyExc_GreenletError_NUM 1 -#define PyExc_GreenletExit_NUM 2 - -#define PyGreenlet_New_NUM 3 -#define PyGreenlet_GetCurrent_NUM 4 -#define PyGreenlet_Throw_NUM 5 -#define PyGreenlet_Switch_NUM 6 -#define PyGreenlet_SetParent_NUM 7 - -#define PyGreenlet_MAIN_NUM 8 -#define PyGreenlet_STARTED_NUM 9 -#define PyGreenlet_ACTIVE_NUM 10 -#define PyGreenlet_GET_PARENT_NUM 11 - -#ifndef GREENLET_MODULE -/* This section is used by modules that uses the greenlet C API */ -static void** _PyGreenlet_API = NULL; - -# define PyGreenlet_Type \ - (*(PyTypeObject*)_PyGreenlet_API[PyGreenlet_Type_NUM]) - -# define PyExc_GreenletError \ - ((PyObject*)_PyGreenlet_API[PyExc_GreenletError_NUM]) - -# define PyExc_GreenletExit \ - ((PyObject*)_PyGreenlet_API[PyExc_GreenletExit_NUM]) - -/* - * PyGreenlet_New(PyObject *args) - * - * greenlet.greenlet(run, parent=None) - */ -# define PyGreenlet_New \ - (*(PyGreenlet * (*)(PyObject * run, PyGreenlet * parent)) \ - _PyGreenlet_API[PyGreenlet_New_NUM]) - -/* - * PyGreenlet_GetCurrent(void) - * - * greenlet.getcurrent() - */ -# define PyGreenlet_GetCurrent \ - (*(PyGreenlet * (*)(void)) _PyGreenlet_API[PyGreenlet_GetCurrent_NUM]) - -/* - * PyGreenlet_Throw( - * PyGreenlet *greenlet, - * PyObject *typ, - * PyObject *val, - * PyObject *tb) - * - * g.throw(...) - */ -# define PyGreenlet_Throw \ - (*(PyObject * (*)(PyGreenlet * self, \ - PyObject * typ, \ - PyObject * val, \ - PyObject * tb)) \ - _PyGreenlet_API[PyGreenlet_Throw_NUM]) - -/* - * PyGreenlet_Switch(PyGreenlet *greenlet, PyObject *args) - * - * g.switch(*args, **kwargs) - */ -# define PyGreenlet_Switch \ - (*(PyObject * \ - (*)(PyGreenlet * greenlet, PyObject * args, PyObject * kwargs)) \ - _PyGreenlet_API[PyGreenlet_Switch_NUM]) - -/* - * PyGreenlet_SetParent(PyObject *greenlet, PyObject *new_parent) - * - * g.parent = new_parent - */ -# define PyGreenlet_SetParent \ - (*(int (*)(PyGreenlet * greenlet, PyGreenlet * nparent)) \ - _PyGreenlet_API[PyGreenlet_SetParent_NUM]) - -/* - * PyGreenlet_GetParent(PyObject* greenlet) - * - * return greenlet.parent; - * - * This could return NULL even if there is no exception active. - * If it does not return NULL, you are responsible for decrementing the - * reference count. - */ -# define PyGreenlet_GetParent \ - (*(PyGreenlet* (*)(PyGreenlet*)) \ - _PyGreenlet_API[PyGreenlet_GET_PARENT_NUM]) - -/* - * deprecated, undocumented alias. - */ -# define PyGreenlet_GET_PARENT PyGreenlet_GetParent - -# define PyGreenlet_MAIN \ - (*(int (*)(PyGreenlet*)) \ - _PyGreenlet_API[PyGreenlet_MAIN_NUM]) - -# define PyGreenlet_STARTED \ - (*(int (*)(PyGreenlet*)) \ - _PyGreenlet_API[PyGreenlet_STARTED_NUM]) - -# define PyGreenlet_ACTIVE \ - (*(int (*)(PyGreenlet*)) \ - _PyGreenlet_API[PyGreenlet_ACTIVE_NUM]) - - - - -/* Macro that imports greenlet and initializes C API */ -/* NOTE: This has actually moved to ``greenlet._greenlet._C_API``, but we - keep the older definition to be sure older code that might have a copy of - the header still works. */ -# define PyGreenlet_Import() \ - { \ - _PyGreenlet_API = (void**)PyCapsule_Import("greenlet._C_API", 0); \ - } - -#endif /* GREENLET_MODULE */ - -#ifdef __cplusplus -} -#endif -#endif /* !Py_GREENLETOBJECT_H */ diff --git a/src/myenv/Scripts/Activate.ps1 b/src/myenv/Scripts/Activate.ps1 deleted file mode 100644 index 5e49b7e..0000000 --- a/src/myenv/Scripts/Activate.ps1 +++ /dev/null @@ -1,502 +0,0 @@ -<# -.Synopsis -Activate a Python virtual environment for the current PowerShell session. - -.Description -Pushes the python executable for a virtual environment to the front of the -$Env:PATH environment variable and sets the prompt to signify that you are -in a Python virtual environment. Makes use of the command line switches as -well as the `pyvenv.cfg` file values present in the virtual environment. - -.Parameter VenvDir -Path to the directory that contains the virtual environment to activate. The -default value for this is the parent of the directory that the Activate.ps1 -script is located within. - -.Parameter Prompt -The prompt prefix to display when this virtual environment is activated. By -default, this prompt is the name of the virtual environment folder (VenvDir) -surrounded by parentheses and followed by a single space (ie. '(.venv) '). - -.Example -Activate.ps1 -Activates the Python virtual environment that contains the Activate.ps1 script. - -.Example -Activate.ps1 -Verbose -Activates the Python virtual environment that contains the Activate.ps1 script, -and shows extra information about the activation as it executes. - -.Example -Activate.ps1 -VenvDir C:\Users\MyUser\Common\.venv -Activates the Python virtual environment located in the specified location. - -.Example -Activate.ps1 -Prompt "MyPython" -Activates the Python virtual environment that contains the Activate.ps1 script, -and prefixes the current prompt with the specified string (surrounded in -parentheses) while the virtual environment is active. - -.Notes -On Windows, it may be required to enable this Activate.ps1 script by setting the -execution policy for the user. You can do this by issuing the following PowerShell -command: - -PS C:\> Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUser - -For more information on Execution Policies: -https://go.microsoft.com/fwlink/?LinkID=135170 - -#> -Param( - [Parameter(Mandatory = $false)] - [String] - $VenvDir, - [Parameter(Mandatory = $false)] - [String] - $Prompt -) - -<# Function declarations --------------------------------------------------- #> - -<# -.Synopsis -Remove all shell session elements added by the Activate script, including the -addition of the virtual environment's Python executable from the beginning of -the PATH variable. - -.Parameter NonDestructive -If present, do not remove this function from the global namespace for the -session. - -#> -function global:deactivate ([switch]$NonDestructive) { - # Revert to original values - - # The prior prompt: - if (Test-Path -Path Function:_OLD_VIRTUAL_PROMPT) { - Copy-Item -Path Function:_OLD_VIRTUAL_PROMPT -Destination Function:prompt - Remove-Item -Path Function:_OLD_VIRTUAL_PROMPT - } - - # The prior PYTHONHOME: - if (Test-Path -Path Env:_OLD_VIRTUAL_PYTHONHOME) { - Copy-Item -Path Env:_OLD_VIRTUAL_PYTHONHOME -Destination Env:PYTHONHOME - Remove-Item -Path Env:_OLD_VIRTUAL_PYTHONHOME - } - - # The prior PATH: - if (Test-Path -Path Env:_OLD_VIRTUAL_PATH) { - Copy-Item -Path Env:_OLD_VIRTUAL_PATH -Destination Env:PATH - Remove-Item -Path Env:_OLD_VIRTUAL_PATH - } - - # Just remove the VIRTUAL_ENV altogether: - if (Test-Path -Path Env:VIRTUAL_ENV) { - Remove-Item -Path env:VIRTUAL_ENV - } - - # Just remove VIRTUAL_ENV_PROMPT altogether. - if (Test-Path -Path Env:VIRTUAL_ENV_PROMPT) { - Remove-Item -Path env:VIRTUAL_ENV_PROMPT - } - - # Just remove the _PYTHON_VENV_PROMPT_PREFIX altogether: - if (Get-Variable -Name "_PYTHON_VENV_PROMPT_PREFIX" -ErrorAction SilentlyContinue) { - Remove-Variable -Name _PYTHON_VENV_PROMPT_PREFIX -Scope Global -Force - } - - # Leave deactivate function in the global namespace if requested: - if (-not $NonDestructive) { - Remove-Item -Path function:deactivate - } -} - -<# -.Description -Get-PyVenvConfig parses the values from the pyvenv.cfg file located in the -given folder, and returns them in a map. - -For each line in the pyvenv.cfg file, if that line can be parsed into exactly -two strings separated by `=` (with any amount of whitespace surrounding the =) -then it is considered a `key = value` line. The left hand string is the key, -the right hand is the value. - -If the value starts with a `'` or a `"` then the first and last character is -stripped from the value before being captured. - -.Parameter ConfigDir -Path to the directory that contains the `pyvenv.cfg` file. -#> -function Get-PyVenvConfig( - [String] - $ConfigDir -) { - Write-Verbose "Given ConfigDir=$ConfigDir, obtain values in pyvenv.cfg" - - # Ensure the file exists, and issue a warning if it doesn't (but still allow the function to continue). - $pyvenvConfigPath = Join-Path -Resolve -Path $ConfigDir -ChildPath 'pyvenv.cfg' -ErrorAction Continue - - # An empty map will be returned if no config file is found. - $pyvenvConfig = @{ } - - if ($pyvenvConfigPath) { - - Write-Verbose "File exists, parse `key = value` lines" - $pyvenvConfigContent = Get-Content -Path $pyvenvConfigPath - - $pyvenvConfigContent | ForEach-Object { - $keyval = $PSItem -split "\s*=\s*", 2 - if ($keyval[0] -and $keyval[1]) { - $val = $keyval[1] - - # Remove extraneous quotations around a string value. - if ("'""".Contains($val.Substring(0, 1))) { - $val = $val.Substring(1, $val.Length - 2) - } - - $pyvenvConfig[$keyval[0]] = $val - Write-Verbose "Adding Key: '$($keyval[0])'='$val'" - } - } - } - return $pyvenvConfig -} - - -<# Begin Activate script --------------------------------------------------- #> - -# Determine the containing directory of this script -$VenvExecPath = Split-Path -Parent $MyInvocation.MyCommand.Definition -$VenvExecDir = Get-Item -Path $VenvExecPath - -Write-Verbose "Activation script is located in path: '$VenvExecPath'" -Write-Verbose "VenvExecDir Fullname: '$($VenvExecDir.FullName)" -Write-Verbose "VenvExecDir Name: '$($VenvExecDir.Name)" - -# Set values required in priority: CmdLine, ConfigFile, Default -# First, get the location of the virtual environment, it might not be -# VenvExecDir if specified on the command line. -if ($VenvDir) { - Write-Verbose "VenvDir given as parameter, using '$VenvDir' to determine values" -} -else { - Write-Verbose "VenvDir not given as a parameter, using parent directory name as VenvDir." - $VenvDir = $VenvExecDir.Parent.FullName.TrimEnd("\\/") - Write-Verbose "VenvDir=$VenvDir" -} - -# Next, read the `pyvenv.cfg` file to determine any required value such -# as `prompt`. -$pyvenvCfg = Get-PyVenvConfig -ConfigDir $VenvDir - -# Next, set the prompt from the command line, or the config file, or -# just use the name of the virtual environment folder. -if ($Prompt) { - Write-Verbose "Prompt specified as argument, using '$Prompt'" -} -else { - Write-Verbose "Prompt not specified as argument to script, checking pyvenv.cfg value" - if ($pyvenvCfg -and $pyvenvCfg['prompt']) { - Write-Verbose " Setting based on value in pyvenv.cfg='$($pyvenvCfg['prompt'])'" - $Prompt = $pyvenvCfg['prompt']; - } - else { - Write-Verbose " Setting prompt based on parent's directory's name. (Is the directory name passed to venv module when creating the virtual environment)" - Write-Verbose " Got leaf-name of $VenvDir='$(Split-Path -Path $venvDir -Leaf)'" - $Prompt = Split-Path -Path $venvDir -Leaf - } -} - -Write-Verbose "Prompt = '$Prompt'" -Write-Verbose "VenvDir='$VenvDir'" - -# Deactivate any currently active virtual environment, but leave the -# deactivate function in place. -deactivate -nondestructive - -# Now set the environment variable VIRTUAL_ENV, used by many tools to determine -# that there is an activated venv. -$env:VIRTUAL_ENV = $VenvDir - -if (-not $Env:VIRTUAL_ENV_DISABLE_PROMPT) { - - Write-Verbose "Setting prompt to '$Prompt'" - - # Set the prompt to include the env name - # Make sure _OLD_VIRTUAL_PROMPT is global - function global:_OLD_VIRTUAL_PROMPT { "" } - Copy-Item -Path function:prompt -Destination function:_OLD_VIRTUAL_PROMPT - New-Variable -Name _PYTHON_VENV_PROMPT_PREFIX -Description "Python virtual environment prompt prefix" -Scope Global -Option ReadOnly -Visibility Public -Value $Prompt - - function global:prompt { - Write-Host -NoNewline -ForegroundColor Green "($_PYTHON_VENV_PROMPT_PREFIX) " - _OLD_VIRTUAL_PROMPT - } - $env:VIRTUAL_ENV_PROMPT = $Prompt -} - -# Clear PYTHONHOME -if (Test-Path -Path Env:PYTHONHOME) { - Copy-Item -Path Env:PYTHONHOME -Destination Env:_OLD_VIRTUAL_PYTHONHOME - Remove-Item -Path Env:PYTHONHOME -} - -# Add the venv to the PATH -Copy-Item -Path Env:PATH -Destination Env:_OLD_VIRTUAL_PATH -$Env:PATH = "$VenvExecDir$([System.IO.Path]::PathSeparator)$Env:PATH" - -# SIG # Begin signature block -# MIIvIwYJKoZIhvcNAQcCoIIvFDCCLxACAQExDzANBglghkgBZQMEAgEFADB5Bgor -# BgEEAYI3AgEEoGswaTA0BgorBgEEAYI3AgEeMCYCAwEAAAQQH8w7YFlLCE63JNLG -# KX7zUQIBAAIBAAIBAAIBAAIBADAxMA0GCWCGSAFlAwQCAQUABCBnL745ElCYk8vk -# dBtMuQhLeWJ3ZGfzKW4DHCYzAn+QB6CCE8MwggWQMIIDeKADAgECAhAFmxtXno4h -# MuI5B72nd3VcMA0GCSqGSIb3DQEBDAUAMGIxCzAJBgNVBAYTAlVTMRUwEwYDVQQK -# EwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5jb20xITAfBgNV -# BAMTGERpZ2lDZXJ0IFRydXN0ZWQgUm9vdCBHNDAeFw0xMzA4MDExMjAwMDBaFw0z -# ODAxMTUxMjAwMDBaMGIxCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2VydCBJ -# bmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5jb20xITAfBgNVBAMTGERpZ2lDZXJ0 -# IFRydXN0ZWQgUm9vdCBHNDCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIB -# AL/mkHNo3rvkXUo8MCIwaTPswqclLskhPfKK2FnC4SmnPVirdprNrnsbhA3EMB/z -# G6Q4FutWxpdtHauyefLKEdLkX9YFPFIPUh/GnhWlfr6fqVcWWVVyr2iTcMKyunWZ -# anMylNEQRBAu34LzB4TmdDttceItDBvuINXJIB1jKS3O7F5OyJP4IWGbNOsFxl7s -# Wxq868nPzaw0QF+xembud8hIqGZXV59UWI4MK7dPpzDZVu7Ke13jrclPXuU15zHL -# 2pNe3I6PgNq2kZhAkHnDeMe2scS1ahg4AxCN2NQ3pC4FfYj1gj4QkXCrVYJBMtfb -# BHMqbpEBfCFM1LyuGwN1XXhm2ToxRJozQL8I11pJpMLmqaBn3aQnvKFPObURWBf3 -# JFxGj2T3wWmIdph2PVldQnaHiZdpekjw4KISG2aadMreSx7nDmOu5tTvkpI6nj3c -# AORFJYm2mkQZK37AlLTSYW3rM9nF30sEAMx9HJXDj/chsrIRt7t/8tWMcCxBYKqx -# YxhElRp2Yn72gLD76GSmM9GJB+G9t+ZDpBi4pncB4Q+UDCEdslQpJYls5Q5SUUd0 -# viastkF13nqsX40/ybzTQRESW+UQUOsxxcpyFiIJ33xMdT9j7CFfxCBRa2+xq4aL -# T8LWRV+dIPyhHsXAj6KxfgommfXkaS+YHS312amyHeUbAgMBAAGjQjBAMA8GA1Ud -# EwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgGGMB0GA1UdDgQWBBTs1+OC0nFdZEzf -# Lmc/57qYrhwPTzANBgkqhkiG9w0BAQwFAAOCAgEAu2HZfalsvhfEkRvDoaIAjeNk -# aA9Wz3eucPn9mkqZucl4XAwMX+TmFClWCzZJXURj4K2clhhmGyMNPXnpbWvWVPjS -# PMFDQK4dUPVS/JA7u5iZaWvHwaeoaKQn3J35J64whbn2Z006Po9ZOSJTROvIXQPK -# 7VB6fWIhCoDIc2bRoAVgX+iltKevqPdtNZx8WorWojiZ83iL9E3SIAveBO6Mm0eB -# cg3AFDLvMFkuruBx8lbkapdvklBtlo1oepqyNhR6BvIkuQkRUNcIsbiJeoQjYUIp -# 5aPNoiBB19GcZNnqJqGLFNdMGbJQQXE9P01wI4YMStyB0swylIQNCAmXHE/A7msg -# dDDS4Dk0EIUhFQEI6FUy3nFJ2SgXUE3mvk3RdazQyvtBuEOlqtPDBURPLDab4vri -# RbgjU2wGb2dVf0a1TD9uKFp5JtKkqGKX0h7i7UqLvBv9R0oN32dmfrJbQdA75PQ7 -# 9ARj6e/CVABRoIoqyc54zNXqhwQYs86vSYiv85KZtrPmYQ/ShQDnUBrkG5WdGaG5 -# nLGbsQAe79APT0JsyQq87kP6OnGlyE0mpTX9iV28hWIdMtKgK1TtmlfB2/oQzxm3 -# i0objwG2J5VT6LaJbVu8aNQj6ItRolb58KaAoNYes7wPD1N1KarqE3fk3oyBIa0H -# EEcRrYc9B9F1vM/zZn4wggawMIIEmKADAgECAhAIrUCyYNKcTJ9ezam9k67ZMA0G -# CSqGSIb3DQEBDAUAMGIxCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2VydCBJ -# bmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5jb20xITAfBgNVBAMTGERpZ2lDZXJ0 -# IFRydXN0ZWQgUm9vdCBHNDAeFw0yMTA0MjkwMDAwMDBaFw0zNjA0MjgyMzU5NTla -# MGkxCzAJBgNVBAYTAlVTMRcwFQYDVQQKEw5EaWdpQ2VydCwgSW5jLjFBMD8GA1UE -# AxM4RGlnaUNlcnQgVHJ1c3RlZCBHNCBDb2RlIFNpZ25pbmcgUlNBNDA5NiBTSEEz -# ODQgMjAyMSBDQTEwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDVtC9C -# 0CiteLdd1TlZG7GIQvUzjOs9gZdwxbvEhSYwn6SOaNhc9es0JAfhS0/TeEP0F9ce -# 2vnS1WcaUk8OoVf8iJnBkcyBAz5NcCRks43iCH00fUyAVxJrQ5qZ8sU7H/Lvy0da -# E6ZMswEgJfMQ04uy+wjwiuCdCcBlp/qYgEk1hz1RGeiQIXhFLqGfLOEYwhrMxe6T -# SXBCMo/7xuoc82VokaJNTIIRSFJo3hC9FFdd6BgTZcV/sk+FLEikVoQ11vkunKoA -# FdE3/hoGlMJ8yOobMubKwvSnowMOdKWvObarYBLj6Na59zHh3K3kGKDYwSNHR7Oh -# D26jq22YBoMbt2pnLdK9RBqSEIGPsDsJ18ebMlrC/2pgVItJwZPt4bRc4G/rJvmM -# 1bL5OBDm6s6R9b7T+2+TYTRcvJNFKIM2KmYoX7BzzosmJQayg9Rc9hUZTO1i4F4z -# 8ujo7AqnsAMrkbI2eb73rQgedaZlzLvjSFDzd5Ea/ttQokbIYViY9XwCFjyDKK05 -# huzUtw1T0PhH5nUwjewwk3YUpltLXXRhTT8SkXbev1jLchApQfDVxW0mdmgRQRNY -# mtwmKwH0iU1Z23jPgUo+QEdfyYFQc4UQIyFZYIpkVMHMIRroOBl8ZhzNeDhFMJlP -# /2NPTLuqDQhTQXxYPUez+rbsjDIJAsxsPAxWEQIDAQABo4IBWTCCAVUwEgYDVR0T -# AQH/BAgwBgEB/wIBADAdBgNVHQ4EFgQUaDfg67Y7+F8Rhvv+YXsIiGX0TkIwHwYD -# VR0jBBgwFoAU7NfjgtJxXWRM3y5nP+e6mK4cD08wDgYDVR0PAQH/BAQDAgGGMBMG -# A1UdJQQMMAoGCCsGAQUFBwMDMHcGCCsGAQUFBwEBBGswaTAkBggrBgEFBQcwAYYY -# aHR0cDovL29jc3AuZGlnaWNlcnQuY29tMEEGCCsGAQUFBzAChjVodHRwOi8vY2Fj -# ZXJ0cy5kaWdpY2VydC5jb20vRGlnaUNlcnRUcnVzdGVkUm9vdEc0LmNydDBDBgNV -# HR8EPDA6MDigNqA0hjJodHRwOi8vY3JsMy5kaWdpY2VydC5jb20vRGlnaUNlcnRU -# cnVzdGVkUm9vdEc0LmNybDAcBgNVHSAEFTATMAcGBWeBDAEDMAgGBmeBDAEEATAN -# BgkqhkiG9w0BAQwFAAOCAgEAOiNEPY0Idu6PvDqZ01bgAhql+Eg08yy25nRm95Ry -# sQDKr2wwJxMSnpBEn0v9nqN8JtU3vDpdSG2V1T9J9Ce7FoFFUP2cvbaF4HZ+N3HL -# IvdaqpDP9ZNq4+sg0dVQeYiaiorBtr2hSBh+3NiAGhEZGM1hmYFW9snjdufE5Btf -# Q/g+lP92OT2e1JnPSt0o618moZVYSNUa/tcnP/2Q0XaG3RywYFzzDaju4ImhvTnh -# OE7abrs2nfvlIVNaw8rpavGiPttDuDPITzgUkpn13c5UbdldAhQfQDN8A+KVssIh -# dXNSy0bYxDQcoqVLjc1vdjcshT8azibpGL6QB7BDf5WIIIJw8MzK7/0pNVwfiThV -# 9zeKiwmhywvpMRr/LhlcOXHhvpynCgbWJme3kuZOX956rEnPLqR0kq3bPKSchh/j -# wVYbKyP/j7XqiHtwa+aguv06P0WmxOgWkVKLQcBIhEuWTatEQOON8BUozu3xGFYH -# Ki8QxAwIZDwzj64ojDzLj4gLDb879M4ee47vtevLt/B3E+bnKD+sEq6lLyJsQfmC -# XBVmzGwOysWGw/YmMwwHS6DTBwJqakAwSEs0qFEgu60bhQjiWQ1tygVQK+pKHJ6l -# /aCnHwZ05/LWUpD9r4VIIflXO7ScA+2GRfS0YW6/aOImYIbqyK+p/pQd52MbOoZW -# eE4wggd3MIIFX6ADAgECAhAHHxQbizANJfMU6yMM0NHdMA0GCSqGSIb3DQEBCwUA -# MGkxCzAJBgNVBAYTAlVTMRcwFQYDVQQKEw5EaWdpQ2VydCwgSW5jLjFBMD8GA1UE -# AxM4RGlnaUNlcnQgVHJ1c3RlZCBHNCBDb2RlIFNpZ25pbmcgUlNBNDA5NiBTSEEz -# ODQgMjAyMSBDQTEwHhcNMjIwMTE3MDAwMDAwWhcNMjUwMTE1MjM1OTU5WjB8MQsw -# CQYDVQQGEwJVUzEPMA0GA1UECBMGT3JlZ29uMRIwEAYDVQQHEwlCZWF2ZXJ0b24x -# IzAhBgNVBAoTGlB5dGhvbiBTb2Z0d2FyZSBGb3VuZGF0aW9uMSMwIQYDVQQDExpQ -# eXRob24gU29mdHdhcmUgRm91bmRhdGlvbjCCAiIwDQYJKoZIhvcNAQEBBQADggIP -# ADCCAgoCggIBAKgc0BTT+iKbtK6f2mr9pNMUTcAJxKdsuOiSYgDFfwhjQy89koM7 -# uP+QV/gwx8MzEt3c9tLJvDccVWQ8H7mVsk/K+X+IufBLCgUi0GGAZUegEAeRlSXx -# xhYScr818ma8EvGIZdiSOhqjYc4KnfgfIS4RLtZSrDFG2tN16yS8skFa3IHyvWdb -# D9PvZ4iYNAS4pjYDRjT/9uzPZ4Pan+53xZIcDgjiTwOh8VGuppxcia6a7xCyKoOA -# GjvCyQsj5223v1/Ig7Dp9mGI+nh1E3IwmyTIIuVHyK6Lqu352diDY+iCMpk9Zanm -# SjmB+GMVs+H/gOiofjjtf6oz0ki3rb7sQ8fTnonIL9dyGTJ0ZFYKeb6BLA66d2GA -# LwxZhLe5WH4Np9HcyXHACkppsE6ynYjTOd7+jN1PRJahN1oERzTzEiV6nCO1M3U1 -# HbPTGyq52IMFSBM2/07WTJSbOeXjvYR7aUxK9/ZkJiacl2iZI7IWe7JKhHohqKuc -# eQNyOzxTakLcRkzynvIrk33R9YVqtB4L6wtFxhUjvDnQg16xot2KVPdfyPAWd81w -# tZADmrUtsZ9qG79x1hBdyOl4vUtVPECuyhCxaw+faVjumapPUnwo8ygflJJ74J+B -# Yxf6UuD7m8yzsfXWkdv52DjL74TxzuFTLHPyARWCSCAbzn3ZIly+qIqDAgMBAAGj -# ggIGMIICAjAfBgNVHSMEGDAWgBRoN+Drtjv4XxGG+/5hewiIZfROQjAdBgNVHQ4E -# FgQUt/1Teh2XDuUj2WW3siYWJgkZHA8wDgYDVR0PAQH/BAQDAgeAMBMGA1UdJQQM -# MAoGCCsGAQUFBwMDMIG1BgNVHR8Ega0wgaowU6BRoE+GTWh0dHA6Ly9jcmwzLmRp -# Z2ljZXJ0LmNvbS9EaWdpQ2VydFRydXN0ZWRHNENvZGVTaWduaW5nUlNBNDA5NlNI -# QTM4NDIwMjFDQTEuY3JsMFOgUaBPhk1odHRwOi8vY3JsNC5kaWdpY2VydC5jb20v -# RGlnaUNlcnRUcnVzdGVkRzRDb2RlU2lnbmluZ1JTQTQwOTZTSEEzODQyMDIxQ0Ex -# LmNybDA+BgNVHSAENzA1MDMGBmeBDAEEATApMCcGCCsGAQUFBwIBFhtodHRwOi8v -# d3d3LmRpZ2ljZXJ0LmNvbS9DUFMwgZQGCCsGAQUFBwEBBIGHMIGEMCQGCCsGAQUF -# BzABhhhodHRwOi8vb2NzcC5kaWdpY2VydC5jb20wXAYIKwYBBQUHMAKGUGh0dHA6 -# Ly9jYWNlcnRzLmRpZ2ljZXJ0LmNvbS9EaWdpQ2VydFRydXN0ZWRHNENvZGVTaWdu -# aW5nUlNBNDA5NlNIQTM4NDIwMjFDQTEuY3J0MAwGA1UdEwEB/wQCMAAwDQYJKoZI -# hvcNAQELBQADggIBABxv4AeV/5ltkELHSC63fXAFYS5tadcWTiNc2rskrNLrfH1N -# s0vgSZFoQxYBFKI159E8oQQ1SKbTEubZ/B9kmHPhprHya08+VVzxC88pOEvz68nA -# 82oEM09584aILqYmj8Pj7h/kmZNzuEL7WiwFa/U1hX+XiWfLIJQsAHBla0i7QRF2 -# de8/VSF0XXFa2kBQ6aiTsiLyKPNbaNtbcucaUdn6vVUS5izWOXM95BSkFSKdE45O -# q3FForNJXjBvSCpwcP36WklaHL+aHu1upIhCTUkzTHMh8b86WmjRUqbrnvdyR2yd -# I5l1OqcMBjkpPpIV6wcc+KY/RH2xvVuuoHjlUjwq2bHiNoX+W1scCpnA8YTs2d50 -# jDHUgwUo+ciwpffH0Riq132NFmrH3r67VaN3TuBxjI8SIZM58WEDkbeoriDk3hxU -# 8ZWV7b8AW6oyVBGfM06UgkfMb58h+tJPrFx8VI/WLq1dTqMfZOm5cuclMnUHs2uq -# rRNtnV8UfidPBL4ZHkTcClQbCoz0UbLhkiDvIS00Dn+BBcxw/TKqVL4Oaz3bkMSs -# M46LciTeucHY9ExRVt3zy7i149sd+F4QozPqn7FrSVHXmem3r7bjyHTxOgqxRCVa -# 18Vtx7P/8bYSBeS+WHCKcliFCecspusCDSlnRUjZwyPdP0VHxaZg2unjHY3rMYIa -# tjCCGrICAQEwfTBpMQswCQYDVQQGEwJVUzEXMBUGA1UEChMORGlnaUNlcnQsIElu -# Yy4xQTA/BgNVBAMTOERpZ2lDZXJ0IFRydXN0ZWQgRzQgQ29kZSBTaWduaW5nIFJT -# QTQwOTYgU0hBMzg0IDIwMjEgQ0ExAhAHHxQbizANJfMU6yMM0NHdMA0GCWCGSAFl -# AwQCAQUAoIHKMBkGCSqGSIb3DQEJAzEMBgorBgEEAYI3AgEEMBwGCisGAQQBgjcC -# AQsxDjAMBgorBgEEAYI3AgEVMC8GCSqGSIb3DQEJBDEiBCBnAZ6P7YvTwq0fbF62 -# o7E75R0LxsW5OtyYiFESQckLhjBeBgorBgEEAYI3AgEMMVAwTqBIgEYAQgB1AGkA -# bAB0ADoAIABSAGUAbABlAGEAcwBlAF8AdgAzAC4AMQAwAC4AMQAxAF8AMgAwADIA -# MwAwADQAMAA1AC4AMAAxoQKAADANBgkqhkiG9w0BAQEFAASCAgAGUpnYl5pjPDC8 -# uJclKp0WgZwr0W3huu2nUQgdQt24qZVmblWWESswIiqJ5FC7YnGxQ6AA57xsPKgz -# GHAIoJw7ETPQjC1IonI4yvI+/8Aw+RZ7m3eDaKCk/Wbs3as7AFaCoPrjxusZGO4y -# VGY0K5zx9Pi17AepkEA+nteZlNbWRNprY1BdQep4fUVykS7+KoqmI8eiGpJe4mtD -# SlXvap7Dqz3OSBJRyb4DecJeBvBflMdCuC+mjW7wskHm8B1oCjtKgnIzETXJOe9N -# Sw98CEHVWOBDqJyMG0jOs3V5hn0li/+esIfsAEl6xDoO+9GRlQKlZHOTDYf0uJaH -# NCqLuSgpHPz0zSWPQkp1GladJxRWUHaxi7NYznMHblCDH2p8pF1ibpbKvxaxMGX8 -# 0j+vAK/pzUK0HfZaY79scZn6q/kwQWjahFT32onbVH48QFTYUMBKfg1zjnQZtTnU -# Clv+Chk75xkPiyOVyd6frpK8I2jfPkXjSdIkRWGqaOkHcVrhKae8zPH+49Q+UDIX -# wjMmCuIarJzFtqh+Iu6eSlj/72q7/C2bwb0r+HkdaU3dRzxvYOqyQ6g0Cn4g+twh -# VTFKywiUiW6muz5HP7pJ9v3WUU+hpFx5WWb2MYQEO/Qh53iYGmLaT+8OvCuXM8Hm -# gmFbKlK7BtSHpVCOyiYW54YizjVvBaGCFz0wghc5BgorBgEEAYI3AwMBMYIXKTCC -# FyUGCSqGSIb3DQEHAqCCFxYwghcSAgEDMQ8wDQYJYIZIAWUDBAIBBQAwdwYLKoZI -# hvcNAQkQAQSgaARmMGQCAQEGCWCGSAGG/WwHATAxMA0GCWCGSAFlAwQCAQUABCBI -# 1dbHE57ZZcjKKZByi4HxJFntDaj547aEW4zgjY+zlQIQOybzqjbuRhUI00KoSULR -# UBgPMjAyMzA0MDUwMDQ1NDdaoIITBzCCBsAwggSooAMCAQICEAxNaXJLlPo8Kko9 -# KQeAPVowDQYJKoZIhvcNAQELBQAwYzELMAkGA1UEBhMCVVMxFzAVBgNVBAoTDkRp -# Z2lDZXJ0LCBJbmMuMTswOQYDVQQDEzJEaWdpQ2VydCBUcnVzdGVkIEc0IFJTQTQw -# OTYgU0hBMjU2IFRpbWVTdGFtcGluZyBDQTAeFw0yMjA5MjEwMDAwMDBaFw0zMzEx -# MjEyMzU5NTlaMEYxCzAJBgNVBAYTAlVTMREwDwYDVQQKEwhEaWdpQ2VydDEkMCIG -# A1UEAxMbRGlnaUNlcnQgVGltZXN0YW1wIDIwMjIgLSAyMIICIjANBgkqhkiG9w0B -# AQEFAAOCAg8AMIICCgKCAgEAz+ylJjrGqfJru43BDZrboegUhXQzGias0BxVHh42 -# bbySVQxh9J0Jdz0Vlggva2Sk/QaDFteRkjgcMQKW+3KxlzpVrzPsYYrppijbkGNc -# vYlT4DotjIdCriak5Lt4eLl6FuFWxsC6ZFO7KhbnUEi7iGkMiMbxvuAvfTuxylON -# QIMe58tySSgeTIAehVbnhe3yYbyqOgd99qtu5Wbd4lz1L+2N1E2VhGjjgMtqedHS -# EJFGKes+JvK0jM1MuWbIu6pQOA3ljJRdGVq/9XtAbm8WqJqclUeGhXk+DF5mjBoK -# JL6cqtKctvdPbnjEKD+jHA9QBje6CNk1prUe2nhYHTno+EyREJZ+TeHdwq2lfvgt -# Gx/sK0YYoxn2Off1wU9xLokDEaJLu5i/+k/kezbvBkTkVf826uV8MefzwlLE5hZ7 -# Wn6lJXPbwGqZIS1j5Vn1TS+QHye30qsU5Thmh1EIa/tTQznQZPpWz+D0CuYUbWR4 -# u5j9lMNzIfMvwi4g14Gs0/EH1OG92V1LbjGUKYvmQaRllMBY5eUuKZCmt2Fk+tkg -# bBhRYLqmgQ8JJVPxvzvpqwcOagc5YhnJ1oV/E9mNec9ixezhe7nMZxMHmsF47caI -# yLBuMnnHC1mDjcbu9Sx8e47LZInxscS451NeX1XSfRkpWQNO+l3qRXMchH7XzuLU -# OncCAwEAAaOCAYswggGHMA4GA1UdDwEB/wQEAwIHgDAMBgNVHRMBAf8EAjAAMBYG -# A1UdJQEB/wQMMAoGCCsGAQUFBwMIMCAGA1UdIAQZMBcwCAYGZ4EMAQQCMAsGCWCG -# SAGG/WwHATAfBgNVHSMEGDAWgBS6FtltTYUvcyl2mi91jGogj57IbzAdBgNVHQ4E -# FgQUYore0GH8jzEU7ZcLzT0qlBTfUpwwWgYDVR0fBFMwUTBPoE2gS4ZJaHR0cDov -# L2NybDMuZGlnaWNlcnQuY29tL0RpZ2lDZXJ0VHJ1c3RlZEc0UlNBNDA5NlNIQTI1 -# NlRpbWVTdGFtcGluZ0NBLmNybDCBkAYIKwYBBQUHAQEEgYMwgYAwJAYIKwYBBQUH -# MAGGGGh0dHA6Ly9vY3NwLmRpZ2ljZXJ0LmNvbTBYBggrBgEFBQcwAoZMaHR0cDov -# L2NhY2VydHMuZGlnaWNlcnQuY29tL0RpZ2lDZXJ0VHJ1c3RlZEc0UlNBNDA5NlNI -# QTI1NlRpbWVTdGFtcGluZ0NBLmNydDANBgkqhkiG9w0BAQsFAAOCAgEAVaoqGvNG -# 83hXNzD8deNP1oUj8fz5lTmbJeb3coqYw3fUZPwV+zbCSVEseIhjVQlGOQD8adTK -# myn7oz/AyQCbEx2wmIncePLNfIXNU52vYuJhZqMUKkWHSphCK1D8G7WeCDAJ+uQt -# 1wmJefkJ5ojOfRu4aqKbwVNgCeijuJ3XrR8cuOyYQfD2DoD75P/fnRCn6wC6X0qP -# GjpStOq/CUkVNTZZmg9U0rIbf35eCa12VIp0bcrSBWcrduv/mLImlTgZiEQU5QpZ -# omvnIj5EIdI/HMCb7XxIstiSDJFPPGaUr10CU+ue4p7k0x+GAWScAMLpWnR1DT3h -# eYi/HAGXyRkjgNc2Wl+WFrFjDMZGQDvOXTXUWT5Dmhiuw8nLw/ubE19qtcfg8wXD -# Wd8nYiveQclTuf80EGf2JjKYe/5cQpSBlIKdrAqLxksVStOYkEVgM4DgI974A6T2 -# RUflzrgDQkfoQTZxd639ouiXdE4u2h4djFrIHprVwvDGIqhPm73YHJpRxC+a9l+n -# J5e6li6FV8Bg53hWf2rvwpWaSxECyIKcyRoFfLpxtU56mWz06J7UWpjIn7+Nuxhc -# Q/XQKujiYu54BNu90ftbCqhwfvCXhHjjCANdRyxjqCU4lwHSPzra5eX25pvcfizM -# /xdMTQCi2NYBDriL7ubgclWJLCcZYfZ3AYwwggauMIIElqADAgECAhAHNje3JFR8 -# 2Ees/ShmKl5bMA0GCSqGSIb3DQEBCwUAMGIxCzAJBgNVBAYTAlVTMRUwEwYDVQQK -# EwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5jb20xITAfBgNV -# BAMTGERpZ2lDZXJ0IFRydXN0ZWQgUm9vdCBHNDAeFw0yMjAzMjMwMDAwMDBaFw0z -# NzAzMjIyMzU5NTlaMGMxCzAJBgNVBAYTAlVTMRcwFQYDVQQKEw5EaWdpQ2VydCwg -# SW5jLjE7MDkGA1UEAxMyRGlnaUNlcnQgVHJ1c3RlZCBHNCBSU0E0MDk2IFNIQTI1 -# NiBUaW1lU3RhbXBpbmcgQ0EwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoIC -# AQDGhjUGSbPBPXJJUVXHJQPE8pE3qZdRodbSg9GeTKJtoLDMg/la9hGhRBVCX6SI -# 82j6ffOciQt/nR+eDzMfUBMLJnOWbfhXqAJ9/UO0hNoR8XOxs+4rgISKIhjf69o9 -# xBd/qxkrPkLcZ47qUT3w1lbU5ygt69OxtXXnHwZljZQp09nsad/ZkIdGAHvbREGJ -# 3HxqV3rwN3mfXazL6IRktFLydkf3YYMZ3V+0VAshaG43IbtArF+y3kp9zvU5Emfv -# DqVjbOSmxR3NNg1c1eYbqMFkdECnwHLFuk4fsbVYTXn+149zk6wsOeKlSNbwsDET -# qVcplicu9Yemj052FVUmcJgmf6AaRyBD40NjgHt1biclkJg6OBGz9vae5jtb7IHe -# IhTZgirHkr+g3uM+onP65x9abJTyUpURK1h0QCirc0PO30qhHGs4xSnzyqqWc0Jo -# n7ZGs506o9UD4L/wojzKQtwYSH8UNM/STKvvmz3+DrhkKvp1KCRB7UK/BZxmSVJQ -# 9FHzNklNiyDSLFc1eSuo80VgvCONWPfcYd6T/jnA+bIwpUzX6ZhKWD7TA4j+s4/T -# Xkt2ElGTyYwMO1uKIqjBJgj5FBASA31fI7tk42PgpuE+9sJ0sj8eCXbsq11GdeJg -# o1gJASgADoRU7s7pXcheMBK9Rp6103a50g5rmQzSM7TNsQIDAQABo4IBXTCCAVkw -# EgYDVR0TAQH/BAgwBgEB/wIBADAdBgNVHQ4EFgQUuhbZbU2FL3MpdpovdYxqII+e -# yG8wHwYDVR0jBBgwFoAU7NfjgtJxXWRM3y5nP+e6mK4cD08wDgYDVR0PAQH/BAQD -# AgGGMBMGA1UdJQQMMAoGCCsGAQUFBwMIMHcGCCsGAQUFBwEBBGswaTAkBggrBgEF -# BQcwAYYYaHR0cDovL29jc3AuZGlnaWNlcnQuY29tMEEGCCsGAQUFBzAChjVodHRw -# Oi8vY2FjZXJ0cy5kaWdpY2VydC5jb20vRGlnaUNlcnRUcnVzdGVkUm9vdEc0LmNy -# dDBDBgNVHR8EPDA6MDigNqA0hjJodHRwOi8vY3JsMy5kaWdpY2VydC5jb20vRGln -# aUNlcnRUcnVzdGVkUm9vdEc0LmNybDAgBgNVHSAEGTAXMAgGBmeBDAEEAjALBglg -# hkgBhv1sBwEwDQYJKoZIhvcNAQELBQADggIBAH1ZjsCTtm+YqUQiAX5m1tghQuGw -# GC4QTRPPMFPOvxj7x1Bd4ksp+3CKDaopafxpwc8dB+k+YMjYC+VcW9dth/qEICU0 -# MWfNthKWb8RQTGIdDAiCqBa9qVbPFXONASIlzpVpP0d3+3J0FNf/q0+KLHqrhc1D -# X+1gtqpPkWaeLJ7giqzl/Yy8ZCaHbJK9nXzQcAp876i8dU+6WvepELJd6f8oVInw -# 1YpxdmXazPByoyP6wCeCRK6ZJxurJB4mwbfeKuv2nrF5mYGjVoarCkXJ38SNoOeY -# +/umnXKvxMfBwWpx2cYTgAnEtp/Nh4cku0+jSbl3ZpHxcpzpSwJSpzd+k1OsOx0I -# SQ+UzTl63f8lY5knLD0/a6fxZsNBzU+2QJshIUDQtxMkzdwdeDrknq3lNHGS1yZr -# 5Dhzq6YBT70/O3itTK37xJV77QpfMzmHQXh6OOmc4d0j/R0o08f56PGYX/sr2H7y -# Rp11LB4nLCbbbxV7HhmLNriT1ObyF5lZynDwN7+YAN8gFk8n+2BnFqFmut1VwDop -# hrCYoCvtlUG3OtUVmDG0YgkPCr2B2RP+v6TR81fZvAT6gt4y3wSJ8ADNXcL50CN/ -# AAvkdgIm2fBldkKmKYcJRyvmfxqkhQ/8mJb2VVQrH4D6wPIOK+XW+6kvRBVK5xMO -# Hds3OBqhK/bt1nz8MIIFjTCCBHWgAwIBAgIQDpsYjvnQLefv21DiCEAYWjANBgkq -# hkiG9w0BAQwFADBlMQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5j -# MRkwFwYDVQQLExB3d3cuZGlnaWNlcnQuY29tMSQwIgYDVQQDExtEaWdpQ2VydCBB -# c3N1cmVkIElEIFJvb3QgQ0EwHhcNMjIwODAxMDAwMDAwWhcNMzExMTA5MjM1OTU5 -# WjBiMQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQL -# ExB3d3cuZGlnaWNlcnQuY29tMSEwHwYDVQQDExhEaWdpQ2VydCBUcnVzdGVkIFJv -# b3QgRzQwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQC/5pBzaN675F1K -# PDAiMGkz7MKnJS7JIT3yithZwuEppz1Yq3aaza57G4QNxDAf8xukOBbrVsaXbR2r -# snnyyhHS5F/WBTxSD1Ifxp4VpX6+n6lXFllVcq9ok3DCsrp1mWpzMpTREEQQLt+C -# 8weE5nQ7bXHiLQwb7iDVySAdYyktzuxeTsiT+CFhmzTrBcZe7FsavOvJz82sNEBf -# sXpm7nfISKhmV1efVFiODCu3T6cw2Vbuyntd463JT17lNecxy9qTXtyOj4DatpGY -# QJB5w3jHtrHEtWoYOAMQjdjUN6QuBX2I9YI+EJFwq1WCQTLX2wRzKm6RAXwhTNS8 -# rhsDdV14Ztk6MUSaM0C/CNdaSaTC5qmgZ92kJ7yhTzm1EVgX9yRcRo9k98FpiHaY -# dj1ZXUJ2h4mXaXpI8OCiEhtmmnTK3kse5w5jrubU75KSOp493ADkRSWJtppEGSt+ -# wJS00mFt6zPZxd9LBADMfRyVw4/3IbKyEbe7f/LVjHAsQWCqsWMYRJUadmJ+9oCw -# ++hkpjPRiQfhvbfmQ6QYuKZ3AeEPlAwhHbJUKSWJbOUOUlFHdL4mrLZBdd56rF+N -# P8m800ERElvlEFDrMcXKchYiCd98THU/Y+whX8QgUWtvsauGi0/C1kVfnSD8oR7F -# wI+isX4KJpn15GkvmB0t9dmpsh3lGwIDAQABo4IBOjCCATYwDwYDVR0TAQH/BAUw -# AwEB/zAdBgNVHQ4EFgQU7NfjgtJxXWRM3y5nP+e6mK4cD08wHwYDVR0jBBgwFoAU -# Reuir/SSy4IxLVGLp6chnfNtyA8wDgYDVR0PAQH/BAQDAgGGMHkGCCsGAQUFBwEB -# BG0wazAkBggrBgEFBQcwAYYYaHR0cDovL29jc3AuZGlnaWNlcnQuY29tMEMGCCsG -# AQUFBzAChjdodHRwOi8vY2FjZXJ0cy5kaWdpY2VydC5jb20vRGlnaUNlcnRBc3N1 -# cmVkSURSb290Q0EuY3J0MEUGA1UdHwQ+MDwwOqA4oDaGNGh0dHA6Ly9jcmwzLmRp -# Z2ljZXJ0LmNvbS9EaWdpQ2VydEFzc3VyZWRJRFJvb3RDQS5jcmwwEQYDVR0gBAow -# CDAGBgRVHSAAMA0GCSqGSIb3DQEBDAUAA4IBAQBwoL9DXFXnOF+go3QbPbYW1/e/ -# Vwe9mqyhhyzshV6pGrsi+IcaaVQi7aSId229GhT0E0p6Ly23OO/0/4C5+KH38nLe -# JLxSA8hO0Cre+i1Wz/n096wwepqLsl7Uz9FDRJtDIeuWcqFItJnLnU+nBgMTdydE -# 1Od/6Fmo8L8vC6bp8jQ87PcDx4eo0kxAGTVGamlUsLihVo7spNU96LHc/RzY9Hda -# XFSMb++hUD38dglohJ9vytsgjTVgHAIDyyCwrFigDkBjxZgiwbJZ9VVrzyerbHbO -# byMt9H5xaiNrIv8SuFQtJ37YOtnwtoeW/VvRXKwYw02fc7cBqZ9Xql4o4rmUMYID -# djCCA3ICAQEwdzBjMQswCQYDVQQGEwJVUzEXMBUGA1UEChMORGlnaUNlcnQsIElu -# Yy4xOzA5BgNVBAMTMkRpZ2lDZXJ0IFRydXN0ZWQgRzQgUlNBNDA5NiBTSEEyNTYg -# VGltZVN0YW1waW5nIENBAhAMTWlyS5T6PCpKPSkHgD1aMA0GCWCGSAFlAwQCAQUA -# oIHRMBoGCSqGSIb3DQEJAzENBgsqhkiG9w0BCRABBDAcBgkqhkiG9w0BCQUxDxcN -# MjMwNDA1MDA0NTQ3WjArBgsqhkiG9w0BCRACDDEcMBowGDAWBBTzhyJNhjOCkjWp -# lLy9j5bp/hx8czAvBgkqhkiG9w0BCQQxIgQgUjSjrzWa1N9tY3HG2o0Php0YCn7i -# UqqdaCMru/DoqI4wNwYLKoZIhvcNAQkQAi8xKDAmMCQwIgQgx/ThvjIoiSCr4iY6 -# vhrE/E/meBwtZNBMgHVXoCO1tvowDQYJKoZIhvcNAQEBBQAEggIARWFWKOxm+FsN -# OV+ONMrWYC+repZLFGKHc5n3dC+cu+FoAsMy561MGvEBnittRqdypXAfKaZ3Ccj4 -# 82B9mWiPNcm/LzEGj2MF2hCS/SlN+g/h9JPDOVZtXcXsnH9lalQZzJLCOdEpCdKl -# NtEYQhVw48quqNSqm55liXFPZv5atRCLq0yO7CEgGTpK6PdmEZzAavzFLtQnvDJj -# JerOZ5NW99tNaYqkJh/Q7rpB7E1UXJjFWwegaMGR4DqHqySB6RAIlNf5HaCT+3KO -# ICGKrNS3wL9WtBYlLIIEm2//Fo3m2CPfp6D3bzDw4Gjb6+BZZBX/jc++OHFLkTEp -# hB9Z1SyLC3TJa3x+ze7p84q/eYs1xqjRIoy3mkQ9gAndWCktfaOp1wAwP4oySENY -# 0Ztionj+H/iydIQNKscWZ95uj/ZTm79OW67X2hLmGOv0ukNck+FE7tHN8I4Lh6VX -# TvjYh8p2SbGHd5v60wqYgrBm5k/r9cacjaptbfl0iP4lY4jqYKnpD3gAvegh5tA4 -# xCmikLbNT99M080eLf2ES/UGqF8THAfLHZXlrFFcJQ3WvwgoiRCTv2ifVlxUXwxB -# lMOfJY3zIEnrxag0ZMJciZX21rKW4ZFoU50q7Nd9+T830tfjwaJWfSNE9Sisr4id -# UvBU/gsB/5d1HPBlxQfXvxm/TMUDeT4= -# SIG # End signature block diff --git a/src/myenv/Scripts/activate b/src/myenv/Scripts/activate deleted file mode 100644 index 5c9540b..0000000 --- a/src/myenv/Scripts/activate +++ /dev/null @@ -1,69 +0,0 @@ -# This file must be used with "source bin/activate" *from bash* -# you cannot run it directly - -deactivate () { - # reset old environment variables - if [ -n "${_OLD_VIRTUAL_PATH:-}" ] ; then - PATH="${_OLD_VIRTUAL_PATH:-}" - export PATH - unset _OLD_VIRTUAL_PATH - fi - if [ -n "${_OLD_VIRTUAL_PYTHONHOME:-}" ] ; then - PYTHONHOME="${_OLD_VIRTUAL_PYTHONHOME:-}" - export PYTHONHOME - unset _OLD_VIRTUAL_PYTHONHOME - fi - - # This should detect bash and zsh, which have a hash command that must - # be called to get it to forget past commands. Without forgetting - # past commands the $PATH changes we made may not be respected - if [ -n "${BASH:-}" -o -n "${ZSH_VERSION:-}" ] ; then - hash -r 2> /dev/null - fi - - if [ -n "${_OLD_VIRTUAL_PS1:-}" ] ; then - PS1="${_OLD_VIRTUAL_PS1:-}" - export PS1 - unset _OLD_VIRTUAL_PS1 - fi - - unset VIRTUAL_ENV - unset VIRTUAL_ENV_PROMPT - if [ ! "${1:-}" = "nondestructive" ] ; then - # Self destruct! - unset -f deactivate - fi -} - -# unset irrelevant variables -deactivate nondestructive - -VIRTUAL_ENV="F:\DjangoBlog\src\myenv" -export VIRTUAL_ENV - -_OLD_VIRTUAL_PATH="$PATH" -PATH="$VIRTUAL_ENV/Scripts:$PATH" -export PATH - -# unset PYTHONHOME if set -# this will fail if PYTHONHOME is set to the empty string (which is bad anyway) -# could use `if (set -u; : $PYTHONHOME) ;` in bash -if [ -n "${PYTHONHOME:-}" ] ; then - _OLD_VIRTUAL_PYTHONHOME="${PYTHONHOME:-}" - unset PYTHONHOME -fi - -if [ -z "${VIRTUAL_ENV_DISABLE_PROMPT:-}" ] ; then - _OLD_VIRTUAL_PS1="${PS1:-}" - PS1="(myenv) ${PS1:-}" - export PS1 - VIRTUAL_ENV_PROMPT="(myenv) " - export VIRTUAL_ENV_PROMPT -fi - -# This should detect bash and zsh, which have a hash command that must -# be called to get it to forget past commands. Without forgetting -# past commands the $PATH changes we made may not be respected -if [ -n "${BASH:-}" -o -n "${ZSH_VERSION:-}" ] ; then - hash -r 2> /dev/null -fi diff --git a/src/myenv/Scripts/activate.bat b/src/myenv/Scripts/activate.bat deleted file mode 100644 index 5b4f4df..0000000 --- a/src/myenv/Scripts/activate.bat +++ /dev/null @@ -1,34 +0,0 @@ -@echo off - -rem This file is UTF-8 encoded, so we need to update the current code page while executing it -for /f "tokens=2 delims=:." %%a in ('"%SystemRoot%\System32\chcp.com"') do ( - set _OLD_CODEPAGE=%%a -) -if defined _OLD_CODEPAGE ( - "%SystemRoot%\System32\chcp.com" 65001 > nul -) - -set VIRTUAL_ENV=F:\DjangoBlog\src\myenv - -if not defined PROMPT set PROMPT=$P$G - -if defined _OLD_VIRTUAL_PROMPT set PROMPT=%_OLD_VIRTUAL_PROMPT% -if defined _OLD_VIRTUAL_PYTHONHOME set PYTHONHOME=%_OLD_VIRTUAL_PYTHONHOME% - -set _OLD_VIRTUAL_PROMPT=%PROMPT% -set PROMPT=(myenv) %PROMPT% - -if defined PYTHONHOME set _OLD_VIRTUAL_PYTHONHOME=%PYTHONHOME% -set PYTHONHOME= - -if defined _OLD_VIRTUAL_PATH set PATH=%_OLD_VIRTUAL_PATH% -if not defined _OLD_VIRTUAL_PATH set _OLD_VIRTUAL_PATH=%PATH% - -set PATH=%VIRTUAL_ENV%\Scripts;%PATH% -set VIRTUAL_ENV_PROMPT=(myenv) - -:END -if defined _OLD_CODEPAGE ( - "%SystemRoot%\System32\chcp.com" %_OLD_CODEPAGE% > nul - set _OLD_CODEPAGE= -) diff --git a/src/myenv/Scripts/bottle.exe b/src/myenv/Scripts/bottle.exe deleted file mode 100644 index 6342d1b..0000000 Binary files a/src/myenv/Scripts/bottle.exe and /dev/null differ diff --git a/src/myenv/Scripts/bottle.py b/src/myenv/Scripts/bottle.py deleted file mode 100644 index c06c3c9..0000000 --- a/src/myenv/Scripts/bottle.py +++ /dev/null @@ -1,4681 +0,0 @@ -#!F:\djangoblog\src\myenv\scripts\python.exe -# -*- coding: utf-8 -*- -from __future__ import print_function -""" -Bottle is a fast and simple micro-framework for small web applications. It -offers request dispatching (Routes) with URL parameter support, templates, -a built-in HTTP Server and adapters for many third party WSGI/HTTP-server and -template engines - all in a single file and with no dependencies other than the -Python Standard Library. - -Homepage and documentation: http://bottlepy.org/ - -Copyright (c) 2009-2024, Marcel Hellkamp. -License: MIT (see LICENSE for details) -""" - -import sys - -__author__ = 'Marcel Hellkamp' -__version__ = '0.13.4' -__license__ = 'MIT' - -############################################################################### -# Command-line interface ###################################################### -############################################################################### -# INFO: Some server adapters need to monkey-patch std-lib modules before they -# are imported. This is why some of the command-line handling is done here, but -# the actual call to _main() is at the end of the file. - - -def _cli_parse(args): # pragma: no coverage - from argparse import ArgumentParser - - parser = ArgumentParser(prog=args[0], usage="%(prog)s [options] package.module:app") - opt = parser.add_argument - opt("--version", action="store_true", help="show version number.") - opt("-b", "--bind", metavar="ADDRESS", help="bind socket to ADDRESS.") - opt("-s", "--server", default='wsgiref', help="use SERVER as backend.") - opt("-p", "--plugin", action="append", help="install additional plugin/s.") - opt("-c", "--conf", action="append", metavar="FILE", - help="load config values from FILE.") - opt("-C", "--param", action="append", metavar="NAME=VALUE", - help="override config values.") - opt("--debug", action="store_true", help="start server in debug mode.") - opt("--reload", action="store_true", help="auto-reload on file changes.") - opt('app', help='WSGI app entry point.', nargs='?') - - cli_args = parser.parse_args(args[1:]) - - return cli_args, parser - - -def _cli_patch(cli_args): # pragma: no coverage - parsed_args, _ = _cli_parse(cli_args) - opts = parsed_args - if opts.server: - if opts.server.startswith('gevent'): - import gevent.monkey - gevent.monkey.patch_all() - elif opts.server.startswith('eventlet'): - import eventlet - eventlet.monkey_patch() - - -if __name__ == '__main__': - _cli_patch(sys.argv) - -############################################################################### -# Imports and Python 2/3 unification ########################################## -############################################################################### - -import base64, calendar, email.utils, functools, hmac, itertools,\ - mimetypes, os, re, tempfile, threading, time, warnings, weakref, hashlib - -from types import FunctionType -from datetime import date as datedate, datetime, timedelta -from tempfile import NamedTemporaryFile -from traceback import format_exc, print_exc -from unicodedata import normalize - -try: - from ujson import dumps as json_dumps, loads as json_lds -except ImportError: - from json import dumps as json_dumps, loads as json_lds - -py = sys.version_info -py3k = py.major > 2 - -# Lots of stdlib and builtin differences. -if py3k: - import http.client as httplib - import _thread as thread - from urllib.parse import urljoin, SplitResult as UrlSplitResult - from urllib.parse import urlencode, quote as urlquote, unquote as urlunquote - urlunquote = functools.partial(urlunquote, encoding='latin1') - from http.cookies import SimpleCookie, Morsel, CookieError - from collections.abc import MutableMapping as DictMixin - from types import ModuleType as new_module - import pickle - from io import BytesIO - import configparser - from datetime import timezone - UTC = timezone.utc - # getfullargspec was deprecated in 3.5 and un-deprecated in 3.6 - # getargspec was deprecated in 3.0 and removed in 3.11 - from inspect import getfullargspec - def getargspec(func): - spec = getfullargspec(func) - kwargs = makelist(spec[0]) + makelist(spec.kwonlyargs) - return kwargs, spec[1], spec[2], spec[3] - - basestring = str - unicode = str - json_loads = lambda s: json_lds(touni(s)) - callable = lambda x: hasattr(x, '__call__') - imap = map - - def _raise(*a): - raise a[0](a[1]).with_traceback(a[2]) -else: # 2.x - warnings.warn("Python 2 support will be dropped in Bottle 0.14", DeprecationWarning) - import httplib - import thread - from urlparse import urljoin, SplitResult as UrlSplitResult - from urllib import urlencode, quote as urlquote, unquote as urlunquote - from Cookie import SimpleCookie, Morsel, CookieError - from itertools import imap - import cPickle as pickle - from imp import new_module - from StringIO import StringIO as BytesIO - import ConfigParser as configparser - from collections import MutableMapping as DictMixin - from inspect import getargspec - from datetime import tzinfo - - class _UTC(tzinfo): - def utcoffset(self, dt): return timedelta(0) - def tzname(self, dt): return "UTC" - def dst(self, dt): return timedelta(0) - UTC = _UTC() - - unicode = unicode - json_loads = json_lds - - exec(compile('def _raise(*a): raise a[0], a[1], a[2]', '', 'exec')) - -# Some helpers for string/byte handling -def tob(s, enc='utf8'): - if isinstance(s, unicode): - return s.encode(enc) - return b'' if s is None else bytes(s) - - -def touni(s, enc='utf8', err='strict'): - if isinstance(s, bytes): - return s.decode(enc, err) - return unicode("" if s is None else s) - - -tonat = touni if py3k else tob - - -def _stderr(*args): - try: - print(*args, file=sys.stderr) - except (IOError, AttributeError): - pass # Some environments do not allow printing (mod_wsgi) - - -# A bug in functools causes it to break if the wrapper is an instance method -def update_wrapper(wrapper, wrapped, *a, **ka): - try: - functools.update_wrapper(wrapper, wrapped, *a, **ka) - except AttributeError: - pass - -# These helpers are used at module level and need to be defined first. -# And yes, I know PEP-8, but sometimes a lower-case classname makes more sense. - - -def depr(major, minor, cause, fix, stacklevel=3): - text = "Warning: Use of deprecated feature or API. (Deprecated in Bottle-%d.%d)\n"\ - "Cause: %s\n"\ - "Fix: %s\n" % (major, minor, cause, fix) - if DEBUG == 'strict': - raise DeprecationWarning(text) - warnings.warn(text, DeprecationWarning, stacklevel=stacklevel) - return DeprecationWarning(text) - - -def makelist(data): # This is just too handy - if isinstance(data, (tuple, list, set, dict)): - return list(data) - elif data: - return [data] - else: - return [] - - -class DictProperty(object): - """ Property that maps to a key in a local dict-like attribute. """ - - def __init__(self, attr, key=None, read_only=False): - self.attr, self.key, self.read_only = attr, key, read_only - - def __call__(self, func): - functools.update_wrapper(self, func, updated=[]) - self.getter, self.key = func, self.key or func.__name__ - return self - - def __get__(self, obj, cls): - if obj is None: return self - key, storage = self.key, getattr(obj, self.attr) - if key not in storage: storage[key] = self.getter(obj) - return storage[key] - - def __set__(self, obj, value): - if self.read_only: raise AttributeError("Read-Only property.") - getattr(obj, self.attr)[self.key] = value - - def __delete__(self, obj): - if self.read_only: raise AttributeError("Read-Only property.") - del getattr(obj, self.attr)[self.key] - - -class cached_property(object): - """ A property that is only computed once per instance and then replaces - itself with an ordinary attribute. Deleting the attribute resets the - property. """ - - def __init__(self, func): - update_wrapper(self, func) - self.func = func - - def __get__(self, obj, cls): - if obj is None: return self - value = obj.__dict__[self.func.__name__] = self.func(obj) - return value - - -class lazy_attribute(object): - """ A property that caches itself to the class object. """ - - def __init__(self, func): - functools.update_wrapper(self, func, updated=[]) - self.getter = func - - def __get__(self, obj, cls): - value = self.getter(cls) - setattr(cls, self.__name__, value) - return value - - -############################################################################### -# Exceptions and Events ####################################################### -############################################################################### - - -class BottleException(Exception): - """ A base class for exceptions used by bottle. """ - pass - -############################################################################### -# Routing ###################################################################### -############################################################################### - - -class RouteError(BottleException): - """ This is a base class for all routing related exceptions """ - - -class RouteReset(BottleException): - """ If raised by a plugin or request handler, the route is reset and all - plugins are re-applied. """ - - -class RouterUnknownModeError(RouteError): - - pass - - -class RouteSyntaxError(RouteError): - """ The route parser found something not supported by this router. """ - - -class RouteBuildError(RouteError): - """ The route could not be built. """ - - -def _re_flatten(p): - """ Turn all capturing groups in a regular expression pattern into - non-capturing groups. """ - if '(' not in p: - return p - return re.sub(r'(\\*)(\(\?P<[^>]+>|\((?!\?))', lambda m: m.group(0) if - len(m.group(1)) % 2 else m.group(1) + '(?:', p) - - -class Router(object): - """ A Router is an ordered collection of route->target pairs. It is used to - efficiently match WSGI requests against a number of routes and return - the first target that satisfies the request. The target may be anything, - usually a string, ID or callable object. A route consists of a path-rule - and a HTTP method. - - The path-rule is either a static path (e.g. `/contact`) or a dynamic - path that contains wildcards (e.g. `/wiki/`). The wildcard syntax - and details on the matching order are described in docs:`routing`. - """ - - default_pattern = '[^/]+' - default_filter = 're' - - #: The current CPython regexp implementation does not allow more - #: than 99 matching groups per regular expression. - _MAX_GROUPS_PER_PATTERN = 99 - - def __init__(self, strict=False): - self.rules = [] # All rules in order - self._groups = {} # index of regexes to find them in dyna_routes - self.builder = {} # Data structure for the url builder - self.static = {} # Search structure for static routes - self.dyna_routes = {} - self.dyna_regexes = {} # Search structure for dynamic routes - #: If true, static routes are no longer checked first. - self.strict_order = strict - self.filters = { - 're': lambda conf: (_re_flatten(conf or self.default_pattern), - None, None), - 'int': lambda conf: (r'-?\d+', int, lambda x: str(int(x))), - 'float': lambda conf: (r'-?[\d.]+', float, lambda x: str(float(x))), - 'path': lambda conf: (r'.+?', None, None) - } - - def add_filter(self, name, func): - """ Add a filter. The provided function is called with the configuration - string as parameter and must return a (regexp, to_python, to_url) tuple. - The first element is a string, the last two are callables or None. """ - self.filters[name] = func - - rule_syntax = re.compile('(\\\\*)' - '(?:(?::([a-zA-Z_][a-zA-Z_0-9]*)?()(?:#(.*?)#)?)' - '|(?:<([a-zA-Z_][a-zA-Z_0-9]*)?(?::([a-zA-Z_]*)' - '(?::((?:\\\\.|[^\\\\>])+)?)?)?>))') - - def _itertokens(self, rule): - offset, prefix = 0, '' - for match in self.rule_syntax.finditer(rule): - prefix += rule[offset:match.start()] - g = match.groups() - if g[2] is not None: - depr(0, 13, "Use of old route syntax.", - "Use instead of :name in routes.", - stacklevel=4) - if len(g[0]) % 2: # Escaped wildcard - prefix += match.group(0)[len(g[0]):] - offset = match.end() - continue - if prefix: - yield prefix, None, None - name, filtr, conf = g[4:7] if g[2] is None else g[1:4] - yield name, filtr or 'default', conf or None - offset, prefix = match.end(), '' - if offset <= len(rule) or prefix: - yield prefix + rule[offset:], None, None - - def add(self, rule, method, target, name=None): - """ Add a new rule or replace the target for an existing rule. """ - anons = 0 # Number of anonymous wildcards found - keys = [] # Names of keys - pattern = '' # Regular expression pattern with named groups - filters = [] # Lists of wildcard input filters - builder = [] # Data structure for the URL builder - is_static = True - - for key, mode, conf in self._itertokens(rule): - if mode: - is_static = False - if mode == 'default': mode = self.default_filter - mask, in_filter, out_filter = self.filters[mode](conf) - if not key: - pattern += '(?:%s)' % mask - key = 'anon%d' % anons - anons += 1 - else: - pattern += '(?P<%s>%s)' % (key, mask) - keys.append(key) - if in_filter: filters.append((key, in_filter)) - builder.append((key, out_filter or str)) - elif key: - pattern += re.escape(key) - builder.append((None, key)) - - self.builder[rule] = builder - if name: self.builder[name] = builder - - if is_static and not self.strict_order: - self.static.setdefault(method, {}) - self.static[method][self.build(rule)] = (target, None) - return - - try: - re_pattern = re.compile('^(%s)$' % pattern) - re_match = re_pattern.match - except re.error as e: - raise RouteSyntaxError("Could not add Route: %s (%s)" % (rule, e)) - - if filters: - - def getargs(path): - url_args = re_match(path).groupdict() - for name, wildcard_filter in filters: - try: - url_args[name] = wildcard_filter(url_args[name]) - except ValueError: - raise HTTPError(400, 'Path has wrong format.') - return url_args - elif re_pattern.groupindex: - - def getargs(path): - return re_match(path).groupdict() - else: - getargs = None - - flatpat = _re_flatten(pattern) - whole_rule = (rule, flatpat, target, getargs) - - if (flatpat, method) in self._groups: - if DEBUG: - msg = 'Route <%s %s> overwrites a previously defined route' - warnings.warn(msg % (method, rule), RuntimeWarning, stacklevel=3) - self.dyna_routes[method][ - self._groups[flatpat, method]] = whole_rule - else: - self.dyna_routes.setdefault(method, []).append(whole_rule) - self._groups[flatpat, method] = len(self.dyna_routes[method]) - 1 - - self._compile(method) - - def _compile(self, method): - all_rules = self.dyna_routes[method] - comborules = self.dyna_regexes[method] = [] - maxgroups = self._MAX_GROUPS_PER_PATTERN - for x in range(0, len(all_rules), maxgroups): - some = all_rules[x:x + maxgroups] - combined = (flatpat for (_, flatpat, _, _) in some) - combined = '|'.join('(^%s$)' % flatpat for flatpat in combined) - combined = re.compile(combined).match - rules = [(target, getargs) for (_, _, target, getargs) in some] - comborules.append((combined, rules)) - - def build(self, _name, *anons, **query): - """ Build an URL by filling the wildcards in a rule. """ - builder = self.builder.get(_name) - if not builder: - raise RouteBuildError("No route with that name.", _name) - try: - for i, value in enumerate(anons): - query['anon%d' % i] = value - url = ''.join([f(query.pop(n)) if n else f for (n, f) in builder]) - return url if not query else url + '?' + urlencode(query) - except KeyError as E: - raise RouteBuildError('Missing URL argument: %r' % E.args[0]) - - def match(self, environ): - """ Return a (target, url_args) tuple or raise HTTPError(400/404/405). """ - verb = environ['REQUEST_METHOD'].upper() - path = environ['PATH_INFO'] or '/' - - methods = ('PROXY', 'HEAD', 'GET', 'ANY') if verb == 'HEAD' else ('PROXY', verb, 'ANY') - - for method in methods: - if method in self.static and path in self.static[method]: - target, getargs = self.static[method][path] - return target, getargs(path) if getargs else {} - elif method in self.dyna_regexes: - for combined, rules in self.dyna_regexes[method]: - match = combined(path) - if match: - target, getargs = rules[match.lastindex - 1] - return target, getargs(path) if getargs else {} - - # No matching route found. Collect alternative methods for 405 response - allowed = set([]) - nocheck = set(methods) - for method in set(self.static) - nocheck: - if path in self.static[method]: - allowed.add(method) - for method in set(self.dyna_regexes) - allowed - nocheck: - for combined, rules in self.dyna_regexes[method]: - match = combined(path) - if match: - allowed.add(method) - if allowed: - allow_header = ",".join(sorted(allowed)) - raise HTTPError(405, "Method not allowed.", Allow=allow_header) - - # No matching route and no alternative method found. We give up - raise HTTPError(404, "Not found: " + repr(path)) - - -class Route(object): - """ This class wraps a route callback along with route specific metadata and - configuration and applies Plugins on demand. It is also responsible for - turning an URL path rule into a regular expression usable by the Router. - """ - - def __init__(self, app, rule, method, callback, - name=None, - plugins=None, - skiplist=None, **config): - #: The application this route is installed to. - self.app = app - #: The path-rule string (e.g. ``/wiki/``). - self.rule = rule - #: The HTTP method as a string (e.g. ``GET``). - self.method = method - #: The original callback with no plugins applied. Useful for introspection. - self.callback = callback - #: The name of the route (if specified) or ``None``. - self.name = name or None - #: A list of route-specific plugins (see :meth:`Bottle.route`). - self.plugins = plugins or [] - #: A list of plugins to not apply to this route (see :meth:`Bottle.route`). - self.skiplist = skiplist or [] - #: Additional keyword arguments passed to the :meth:`Bottle.route` - #: decorator are stored in this dictionary. Used for route-specific - #: plugin configuration and meta-data. - self.config = app.config._make_overlay() - self.config.load_dict(config) - - @cached_property - def call(self): - """ The route callback with all plugins applied. This property is - created on demand and then cached to speed up subsequent requests.""" - return self._make_callback() - - def reset(self): - """ Forget any cached values. The next time :attr:`call` is accessed, - all plugins are re-applied. """ - self.__dict__.pop('call', None) - - def prepare(self): - """ Do all on-demand work immediately (useful for debugging).""" - self.call - - def all_plugins(self): - """ Yield all Plugins affecting this route. """ - unique = set() - for p in reversed(self.app.plugins + self.plugins): - if True in self.skiplist: break - name = getattr(p, 'name', False) - if name and (name in self.skiplist or name in unique): continue - if p in self.skiplist or type(p) in self.skiplist: continue - if name: unique.add(name) - yield p - - def _make_callback(self): - callback = self.callback - for plugin in self.all_plugins(): - try: - if hasattr(plugin, 'apply'): - callback = plugin.apply(callback, self) - else: - callback = plugin(callback) - except RouteReset: # Try again with changed configuration. - return self._make_callback() - if callback is not self.callback: - update_wrapper(callback, self.callback) - return callback - - def get_undecorated_callback(self): - """ Return the callback. If the callback is a decorated function, try to - recover the original function. """ - func = self.callback - while True: - if getattr(func, '__wrapped__', False): - func = func.__wrapped__ - elif getattr(func, '__func__', False): - func = func.__func__ - elif getattr(func, '__closure__', False): - cells_values = (cell.cell_contents for cell in func.__closure__) - isfunc = lambda x: isinstance(x, FunctionType) or hasattr(x, '__call__') - func = next(iter(filter(isfunc, cells_values)), func) - else: - break - return func - - def get_callback_args(self): - """ Return a list of argument names the callback (most likely) accepts - as keyword arguments. If the callback is a decorated function, try - to recover the original function before inspection. """ - return getargspec(self.get_undecorated_callback())[0] - - def get_config(self, key, default=None): - """ Lookup a config field and return its value, first checking the - route.config, then route.app.config.""" - depr(0, 13, "Route.get_config() is deprecated.", - "The Route.config property already includes values from the" - " application config for missing keys. Access it directly.") - return self.config.get(key, default) - - def __repr__(self): - cb = self.get_undecorated_callback() - return '<%s %s -> %s:%s>' % ( - self.method, self.rule, cb.__module__, getattr(cb, '__name__', '__call__') - ) - -############################################################################### -# Application Object ########################################################### -############################################################################### - - -class Bottle(object): - """ Each Bottle object represents a single, distinct web application and - consists of routes, callbacks, plugins, resources and configuration. - Instances are callable WSGI applications. - - :param catchall: If true (default), handle all exceptions. Turn off to - let debugging middleware handle exceptions. - """ - - @lazy_attribute - def _global_config(cls): - cfg = ConfigDict() - cfg.meta_set('catchall', 'validate', bool) - return cfg - - def __init__(self, **kwargs): - #: A :class:`ConfigDict` for app specific configuration. - self.config = self._global_config._make_overlay() - self.config._add_change_listener( - functools.partial(self.trigger_hook, 'config')) - - self.config.update({ - "catchall": True - }) - - if kwargs.get('catchall') is False: - depr(0, 13, "Bottle(catchall) keyword argument.", - "The 'catchall' setting is now part of the app " - "configuration. Fix: `app.config['catchall'] = False`") - self.config['catchall'] = False - if kwargs.get('autojson') is False: - depr(0, 13, "Bottle(autojson) keyword argument.", - "The 'autojson' setting is now part of the app " - "configuration. Fix: `app.config['json.enable'] = False`") - self.config['json.disable'] = True - - self._mounts = [] - - #: A :class:`ResourceManager` for application files - self.resources = ResourceManager() - - self.routes = [] # List of installed :class:`Route` instances. - self.router = Router() # Maps requests to :class:`Route` instances. - self.error_handler = {} - - # Core plugins - self.plugins = [] # List of installed plugins. - self.install(JSONPlugin()) - self.install(TemplatePlugin()) - - #: If true, most exceptions are caught and returned as :exc:`HTTPError` - catchall = DictProperty('config', 'catchall') - - __hook_names = 'before_request', 'after_request', 'app_reset', 'config' - __hook_reversed = {'after_request'} - - @cached_property - def _hooks(self): - return dict((name, []) for name in self.__hook_names) - - def add_hook(self, name, func): - """ Attach a callback to a hook. Three hooks are currently implemented: - - before_request - Executed once before each request. The request context is - available, but no routing has happened yet. - after_request - Executed once after each request regardless of its outcome. - app_reset - Called whenever :meth:`Bottle.reset` is called. - """ - if name in self.__hook_reversed: - self._hooks[name].insert(0, func) - else: - self._hooks[name].append(func) - - def remove_hook(self, name, func): - """ Remove a callback from a hook. """ - if name in self._hooks and func in self._hooks[name]: - self._hooks[name].remove(func) - return True - - def trigger_hook(self, __name, *args, **kwargs): - """ Trigger a hook and return a list of results. """ - return [hook(*args, **kwargs) for hook in self._hooks[__name][:]] - - def hook(self, name): - """ Return a decorator that attaches a callback to a hook. See - :meth:`add_hook` for details.""" - - def decorator(func): - self.add_hook(name, func) - return func - - return decorator - - def _mount_wsgi(self, prefix, app, **options): - segments = [p for p in prefix.split('/') if p] - if not segments: - raise ValueError('WSGI applications cannot be mounted to "/".') - path_depth = len(segments) - - def mountpoint_wrapper(): - try: - request.path_shift(path_depth) - rs = HTTPResponse([]) - - def start_response(status, headerlist, exc_info=None): - if exc_info: - _raise(*exc_info) - if py3k: - # Errors here mean that the mounted WSGI app did not - # follow PEP-3333 (which requires latin1) or used a - # pre-encoding other than utf8 :/ - status = status.encode('latin1').decode('utf8') - headerlist = [(k, v.encode('latin1').decode('utf8')) - for (k, v) in headerlist] - rs.status = status - for name, value in headerlist: - rs.add_header(name, value) - return rs.body.append - - body = app(request.environ, start_response) - rs.body = itertools.chain(rs.body, body) if rs.body else body - return rs - finally: - request.path_shift(-path_depth) - - options.setdefault('skip', True) - options.setdefault('method', 'PROXY') - options.setdefault('mountpoint', {'prefix': prefix, 'target': app}) - options['callback'] = mountpoint_wrapper - - self.route('/%s/<:re:.*>' % '/'.join(segments), **options) - if not prefix.endswith('/'): - self.route('/' + '/'.join(segments), **options) - - def _mount_app(self, prefix, app, **options): - if app in self._mounts or '_mount.app' in app.config: - depr(0, 13, "Application mounted multiple times. Falling back to WSGI mount.", - "Clone application before mounting to a different location.") - return self._mount_wsgi(prefix, app, **options) - - if options: - depr(0, 13, "Unsupported mount options. Falling back to WSGI mount.", - "Do not specify any route options when mounting bottle application.") - return self._mount_wsgi(prefix, app, **options) - - if not prefix.endswith("/"): - depr(0, 13, "Prefix must end in '/'. Falling back to WSGI mount.", - "Consider adding an explicit redirect from '/prefix' to '/prefix/' in the parent application.") - return self._mount_wsgi(prefix, app, **options) - - self._mounts.append(app) - app.config['_mount.prefix'] = prefix - app.config['_mount.app'] = self - for route in app.routes: - route.rule = prefix + route.rule.lstrip('/') - self.add_route(route) - - def mount(self, prefix, app, **options): - """ Mount an application (:class:`Bottle` or plain WSGI) to a specific - URL prefix. Example:: - - parent_app.mount('/prefix/', child_app) - - :param prefix: path prefix or `mount-point`. - :param app: an instance of :class:`Bottle` or a WSGI application. - - Plugins from the parent application are not applied to the routes - of the mounted child application. If you need plugins in the child - application, install them separately. - - While it is possible to use path wildcards within the prefix path - (:class:`Bottle` childs only), it is highly discouraged. - - The prefix path must end with a slash. If you want to access the - root of the child application via `/prefix` in addition to - `/prefix/`, consider adding a route with a 307 redirect to the - parent application. - """ - - if not prefix.startswith('/'): - raise ValueError("Prefix must start with '/'") - - if isinstance(app, Bottle): - return self._mount_app(prefix, app, **options) - else: - return self._mount_wsgi(prefix, app, **options) - - def merge(self, routes): - """ Merge the routes of another :class:`Bottle` application or a list of - :class:`Route` objects into this application. The routes keep their - 'owner', meaning that the :data:`Route.app` attribute is not - changed. """ - if isinstance(routes, Bottle): - routes = routes.routes - for route in routes: - self.add_route(route) - - def install(self, plugin): - """ Add a plugin to the list of plugins and prepare it for being - applied to all routes of this application. A plugin may be a simple - decorator or an object that implements the :class:`Plugin` API. - """ - if hasattr(plugin, 'setup'): plugin.setup(self) - if not callable(plugin) and not hasattr(plugin, 'apply'): - raise TypeError("Plugins must be callable or implement .apply()") - self.plugins.append(plugin) - self.reset() - return plugin - - def uninstall(self, plugin): - """ Uninstall plugins. Pass an instance to remove a specific plugin, a type - object to remove all plugins that match that type, a string to remove - all plugins with a matching ``name`` attribute or ``True`` to remove all - plugins. Return the list of removed plugins. """ - removed, remove = [], plugin - for i, plugin in list(enumerate(self.plugins))[::-1]: - if remove is True or remove is plugin or remove is type(plugin) \ - or getattr(plugin, 'name', True) == remove: - removed.append(plugin) - del self.plugins[i] - if hasattr(plugin, 'close'): plugin.close() - if removed: self.reset() - return removed - - def reset(self, route=None): - """ Reset all routes (force plugins to be re-applied) and clear all - caches. If an ID or route object is given, only that specific route - is affected. """ - if route is None: routes = self.routes - elif isinstance(route, Route): routes = [route] - else: routes = [self.routes[route]] - for route in routes: - route.reset() - if DEBUG: - for route in routes: - route.prepare() - self.trigger_hook('app_reset') - - def close(self): - """ Close the application and all installed plugins. """ - for plugin in self.plugins: - if hasattr(plugin, 'close'): plugin.close() - - def run(self, **kwargs): - """ Calls :func:`run` with the same parameters. """ - run(self, **kwargs) - - def match(self, environ): - """ Search for a matching route and return a (:class:`Route`, urlargs) - tuple. The second value is a dictionary with parameters extracted - from the URL. Raise :exc:`HTTPError` (404/405) on a non-match.""" - return self.router.match(environ) - - def get_url(self, routename, **kargs): - """ Return a string that matches a named route """ - scriptname = request.environ.get('SCRIPT_NAME', '').strip('/') + '/' - location = self.router.build(routename, **kargs).lstrip('/') - return urljoin(urljoin('/', scriptname), location) - - def add_route(self, route): - """ Add a route object, but do not change the :data:`Route.app` - attribute.""" - self.routes.append(route) - self.router.add(route.rule, route.method, route, name=route.name) - if DEBUG: route.prepare() - - def route(self, - path=None, - method='GET', - callback=None, - name=None, - apply=None, - skip=None, **config): - """ A decorator to bind a function to a request URL. Example:: - - @app.route('/hello/') - def hello(name): - return 'Hello %s' % name - - The ```` part is a wildcard. See :class:`Router` for syntax - details. - - :param path: Request path or a list of paths to listen to. If no - path is specified, it is automatically generated from the - signature of the function. - :param method: HTTP method (`GET`, `POST`, `PUT`, ...) or a list of - methods to listen to. (default: `GET`) - :param callback: An optional shortcut to avoid the decorator - syntax. ``route(..., callback=func)`` equals ``route(...)(func)`` - :param name: The name for this route. (default: None) - :param apply: A decorator or plugin or a list of plugins. These are - applied to the route callback in addition to installed plugins. - :param skip: A list of plugins, plugin classes or names. Matching - plugins are not installed to this route. ``True`` skips all. - - Any additional keyword arguments are stored as route-specific - configuration and passed to plugins (see :meth:`Plugin.apply`). - """ - if callable(path): path, callback = None, path - plugins = makelist(apply) - skiplist = makelist(skip) - - def decorator(callback): - if isinstance(callback, basestring): callback = load(callback) - for rule in makelist(path) or yieldroutes(callback): - for verb in makelist(method): - verb = verb.upper() - route = Route(self, rule, verb, callback, - name=name, - plugins=plugins, - skiplist=skiplist, **config) - self.add_route(route) - return callback - - return decorator(callback) if callback else decorator - - def get(self, path=None, method='GET', **options): - """ Equals :meth:`route`. """ - return self.route(path, method, **options) - - def post(self, path=None, method='POST', **options): - """ Equals :meth:`route` with a ``POST`` method parameter. """ - return self.route(path, method, **options) - - def put(self, path=None, method='PUT', **options): - """ Equals :meth:`route` with a ``PUT`` method parameter. """ - return self.route(path, method, **options) - - def delete(self, path=None, method='DELETE', **options): - """ Equals :meth:`route` with a ``DELETE`` method parameter. """ - return self.route(path, method, **options) - - def patch(self, path=None, method='PATCH', **options): - """ Equals :meth:`route` with a ``PATCH`` method parameter. """ - return self.route(path, method, **options) - - def error(self, code=500, callback=None): - """ Register an output handler for a HTTP error code. Can - be used as a decorator or called directly :: - - def error_handler_500(error): - return 'error_handler_500' - - app.error(code=500, callback=error_handler_500) - - @app.error(404) - def error_handler_404(error): - return 'error_handler_404' - - """ - - def decorator(callback): - if isinstance(callback, basestring): callback = load(callback) - self.error_handler[int(code)] = callback - return callback - - return decorator(callback) if callback else decorator - - def default_error_handler(self, res): - return tob(template(ERROR_PAGE_TEMPLATE, e=res, template_settings=dict(name='__ERROR_PAGE_TEMPLATE'))) - - def _handle(self, environ): - path = environ['bottle.raw_path'] = environ['PATH_INFO'] - if py3k: - environ['PATH_INFO'] = path.encode('latin1').decode('utf8', 'ignore') - - environ['bottle.app'] = self - request.bind(environ) - response.bind() - - try: - while True: # Remove in 0.14 together with RouteReset - out = None - try: - self.trigger_hook('before_request') - route, args = self.router.match(environ) - environ['route.handle'] = route - environ['bottle.route'] = route - environ['route.url_args'] = args - out = route.call(**args) - break - except HTTPResponse as E: - out = E - break - except RouteReset: - depr(0, 13, "RouteReset exception deprecated", - "Call route.call() after route.reset() and " - "return the result.") - route.reset() - continue - finally: - if isinstance(out, HTTPResponse): - out.apply(response) - try: - self.trigger_hook('after_request') - except HTTPResponse as E: - out = E - out.apply(response) - except (KeyboardInterrupt, SystemExit, MemoryError): - raise - except Exception as E: - if not self.catchall: raise - stacktrace = format_exc() - environ['wsgi.errors'].write(stacktrace) - environ['wsgi.errors'].flush() - environ['bottle.exc_info'] = sys.exc_info() - out = HTTPError(500, "Internal Server Error", E, stacktrace) - out.apply(response) - - return out - - def _cast(self, out, peek=None): - """ Try to convert the parameter into something WSGI compatible and set - correct HTTP headers when possible. - Support: False, str, unicode, dict, HTTPResponse, HTTPError, file-like, - iterable of strings and iterable of unicodes - """ - - # Empty output is done here - if not out: - if 'Content-Length' not in response: - response['Content-Length'] = 0 - return [] - # Join lists of byte or unicode strings. Mixed lists are NOT supported - if isinstance(out, (tuple, list))\ - and isinstance(out[0], (bytes, unicode)): - out = out[0][0:0].join(out) # b'abc'[0:0] -> b'' - # Encode unicode strings - if isinstance(out, unicode): - out = out.encode(response.charset) - # Byte Strings are just returned - if isinstance(out, bytes): - if 'Content-Length' not in response: - response['Content-Length'] = len(out) - return [out] - # HTTPError or HTTPException (recursive, because they may wrap anything) - # TODO: Handle these explicitly in handle() or make them iterable. - if isinstance(out, HTTPError): - out.apply(response) - out = self.error_handler.get(out.status_code, - self.default_error_handler)(out) - return self._cast(out) - if isinstance(out, HTTPResponse): - out.apply(response) - return self._cast(out.body) - - # File-like objects. - if hasattr(out, 'read'): - if 'wsgi.file_wrapper' in request.environ: - return request.environ['wsgi.file_wrapper'](out) - elif hasattr(out, 'close') or not hasattr(out, '__iter__'): - return WSGIFileWrapper(out) - - # Handle Iterables. We peek into them to detect their inner type. - try: - iout = iter(out) - first = next(iout) - while not first: - first = next(iout) - except StopIteration: - return self._cast('') - except HTTPResponse as E: - first = E - except (KeyboardInterrupt, SystemExit, MemoryError): - raise - except Exception as error: - if not self.catchall: raise - first = HTTPError(500, 'Unhandled exception', error, format_exc()) - - # These are the inner types allowed in iterator or generator objects. - if isinstance(first, HTTPResponse): - return self._cast(first) - elif isinstance(first, bytes): - new_iter = itertools.chain([first], iout) - elif isinstance(first, unicode): - encoder = lambda x: x.encode(response.charset) - new_iter = imap(encoder, itertools.chain([first], iout)) - else: - msg = 'Unsupported response type: %s' % type(first) - return self._cast(HTTPError(500, msg)) - if hasattr(out, 'close'): - new_iter = _closeiter(new_iter, out.close) - return new_iter - - def wsgi(self, environ, start_response): - """ The bottle WSGI-interface. """ - try: - out = self._cast(self._handle(environ)) - # rfc2616 section 4.3 - if response._status_code in (100, 101, 204, 304)\ - or environ['REQUEST_METHOD'] == 'HEAD': - if hasattr(out, 'close'): out.close() - out = [] - exc_info = environ.get('bottle.exc_info') - if exc_info is not None: - del environ['bottle.exc_info'] - start_response(response._wsgi_status_line(), response.headerlist, exc_info) - return out - except (KeyboardInterrupt, SystemExit, MemoryError): - raise - except Exception as E: - if not self.catchall: raise - err = '

    Critical error while processing request: %s

    ' \ - % html_escape(environ.get('PATH_INFO', '/')) - if DEBUG: - err += '

    Error:

    \n
    \n%s\n
    \n' \ - '

    Traceback:

    \n
    \n%s\n
    \n' \ - % (html_escape(repr(E)), html_escape(format_exc())) - environ['wsgi.errors'].write(err) - environ['wsgi.errors'].flush() - headers = [('Content-Type', 'text/html; charset=UTF-8')] - start_response('500 INTERNAL SERVER ERROR', headers, sys.exc_info()) - return [tob(err)] - - def __call__(self, environ, start_response): - """ Each instance of :class:'Bottle' is a WSGI application. """ - return self.wsgi(environ, start_response) - - def __enter__(self): - """ Use this application as default for all module-level shortcuts. """ - default_app.push(self) - return self - - def __exit__(self, exc_type, exc_value, traceback): - default_app.pop() - - def __setattr__(self, name, value): - if name in self.__dict__: - raise AttributeError("Attribute %s already defined. Plugin conflict?" % name) - object.__setattr__(self, name, value) - -############################################################################### -# HTTP and WSGI Tools ########################################################## -############################################################################### - - -class BaseRequest(object): - """ A wrapper for WSGI environment dictionaries that adds a lot of - convenient access methods and properties. Most of them are read-only. - - Adding new attributes to a request actually adds them to the environ - dictionary (as 'bottle.request.ext.'). This is the recommended - way to store and access request-specific data. - """ - - __slots__ = ('environ', ) - - #: Maximum size of memory buffer for :attr:`body` in bytes. - MEMFILE_MAX = 102400 - - def __init__(self, environ=None): - """ Wrap a WSGI environ dictionary. """ - #: The wrapped WSGI environ dictionary. This is the only real attribute. - #: All other attributes actually are read-only properties. - self.environ = {} if environ is None else environ - self.environ['bottle.request'] = self - - @DictProperty('environ', 'bottle.app', read_only=True) - def app(self): - """ Bottle application handling this request. """ - raise RuntimeError('This request is not connected to an application.') - - @DictProperty('environ', 'bottle.route', read_only=True) - def route(self): - """ The bottle :class:`Route` object that matches this request. """ - raise RuntimeError('This request is not connected to a route.') - - @DictProperty('environ', 'route.url_args', read_only=True) - def url_args(self): - """ The arguments extracted from the URL. """ - raise RuntimeError('This request is not connected to a route.') - - @property - def path(self): - """ The value of ``PATH_INFO`` with exactly one prefixed slash (to fix - broken clients and avoid the "empty path" edge case). """ - return '/' + self.environ.get('PATH_INFO', '').lstrip('/') - - @property - def method(self): - """ The ``REQUEST_METHOD`` value as an uppercase string. """ - return self.environ.get('REQUEST_METHOD', 'GET').upper() - - @DictProperty('environ', 'bottle.request.headers', read_only=True) - def headers(self): - """ A :class:`WSGIHeaderDict` that provides case-insensitive access to - HTTP request headers. """ - return WSGIHeaderDict(self.environ) - - def get_header(self, name, default=None): - """ Return the value of a request header, or a given default value. """ - return self.headers.get(name, default) - - @DictProperty('environ', 'bottle.request.cookies', read_only=True) - def cookies(self): - """ Cookies parsed into a :class:`FormsDict`. Signed cookies are NOT - decoded. Use :meth:`get_cookie` if you expect signed cookies. """ - cookies = SimpleCookie(self.environ.get('HTTP_COOKIE', '')).values() - return FormsDict((c.key, c.value) for c in cookies) - - def get_cookie(self, key, default=None, secret=None, digestmod=hashlib.sha256): - """ Return the content of a cookie. To read a `Signed Cookie`, the - `secret` must match the one used to create the cookie (see - :meth:`BaseResponse.set_cookie`). If anything goes wrong (missing - cookie or wrong signature), return a default value. """ - value = self.cookies.get(key) - if secret: - # See BaseResponse.set_cookie for details on signed cookies. - if value and value.startswith('!') and '?' in value: - sig, msg = map(tob, value[1:].split('?', 1)) - hash = hmac.new(tob(secret), msg, digestmod=digestmod).digest() - if _lscmp(sig, base64.b64encode(hash)): - dst = pickle.loads(base64.b64decode(msg)) - if dst and dst[0] == key: - return dst[1] - return default - return value or default - - @DictProperty('environ', 'bottle.request.query', read_only=True) - def query(self): - """ The :attr:`query_string` parsed into a :class:`FormsDict`. These - values are sometimes called "URL arguments" or "GET parameters", but - not to be confused with "URL wildcards" as they are provided by the - :class:`Router`. """ - get = self.environ['bottle.get'] = FormsDict() - pairs = _parse_qsl(self.environ.get('QUERY_STRING', '')) - for key, value in pairs: - get[key] = value - return get - - @DictProperty('environ', 'bottle.request.forms', read_only=True) - def forms(self): - """ Form values parsed from an `url-encoded` or `multipart/form-data` - encoded POST or PUT request body. The result is returned as a - :class:`FormsDict`. All keys and values are strings. File uploads - are stored separately in :attr:`files`. """ - forms = FormsDict() - forms.recode_unicode = self.POST.recode_unicode - for name, item in self.POST.allitems(): - if not isinstance(item, FileUpload): - forms[name] = item - return forms - - @DictProperty('environ', 'bottle.request.params', read_only=True) - def params(self): - """ A :class:`FormsDict` with the combined values of :attr:`query` and - :attr:`forms`. File uploads are stored in :attr:`files`. """ - params = FormsDict() - for key, value in self.query.allitems(): - params[key] = value - for key, value in self.forms.allitems(): - params[key] = value - return params - - @DictProperty('environ', 'bottle.request.files', read_only=True) - def files(self): - """ File uploads parsed from `multipart/form-data` encoded POST or PUT - request body. The values are instances of :class:`FileUpload`. - - """ - files = FormsDict() - files.recode_unicode = self.POST.recode_unicode - for name, item in self.POST.allitems(): - if isinstance(item, FileUpload): - files[name] = item - return files - - @DictProperty('environ', 'bottle.request.json', read_only=True) - def json(self): - """ If the ``Content-Type`` header is ``application/json`` or - ``application/json-rpc``, this property holds the parsed content - of the request body. Only requests smaller than :attr:`MEMFILE_MAX` - are processed to avoid memory exhaustion. - Invalid JSON raises a 400 error response. - """ - ctype = self.environ.get('CONTENT_TYPE', '').lower().split(';')[0] - if ctype in ('application/json', 'application/json-rpc'): - b = self._get_body_string(self.MEMFILE_MAX) - if not b: - return None - try: - return json_loads(b) - except (ValueError, TypeError): - raise HTTPError(400, 'Invalid JSON') - return None - - def _iter_body(self, read, bufsize): - maxread = max(0, self.content_length) - while maxread: - part = read(min(maxread, bufsize)) - if not part: break - yield part - maxread -= len(part) - - @staticmethod - def _iter_chunked(read, bufsize): - err = HTTPError(400, 'Error while parsing chunked transfer body.') - rn, sem, bs = tob('\r\n'), tob(';'), tob('') - while True: - header = read(1) - while header[-2:] != rn: - c = read(1) - header += c - if not c: raise err - if len(header) > bufsize: raise err - size, _, _ = header.partition(sem) - try: - maxread = int(tonat(size.strip()), 16) - except ValueError: - raise err - if maxread == 0: break - buff = bs - while maxread > 0: - if not buff: - buff = read(min(maxread, bufsize)) - part, buff = buff[:maxread], buff[maxread:] - if not part: raise err - yield part - maxread -= len(part) - if read(2) != rn: - raise err - - @DictProperty('environ', 'bottle.request.body', read_only=True) - def _body(self): - try: - read_func = self.environ['wsgi.input'].read - except KeyError: - self.environ['wsgi.input'] = BytesIO() - return self.environ['wsgi.input'] - body_iter = self._iter_chunked if self.chunked else self._iter_body - body, body_size, is_temp_file = BytesIO(), 0, False - for part in body_iter(read_func, self.MEMFILE_MAX): - body.write(part) - body_size += len(part) - if not is_temp_file and body_size > self.MEMFILE_MAX: - body, tmp = NamedTemporaryFile(mode='w+b'), body - body.write(tmp.getvalue()) - del tmp - is_temp_file = True - self.environ['wsgi.input'] = body - body.seek(0) - return body - - def _get_body_string(self, maxread): - """ Read body into a string. Raise HTTPError(413) on requests that are - too large. """ - if self.content_length > maxread: - raise HTTPError(413, 'Request entity too large') - data = self.body.read(maxread + 1) - if len(data) > maxread: - raise HTTPError(413, 'Request entity too large') - return data - - @property - def body(self): - """ The HTTP request body as a seek-able file-like object. Depending on - :attr:`MEMFILE_MAX`, this is either a temporary file or a - :class:`io.BytesIO` instance. Accessing this property for the first - time reads and replaces the ``wsgi.input`` environ variable. - Subsequent accesses just do a `seek(0)` on the file object. """ - self._body.seek(0) - return self._body - - @property - def chunked(self): - """ True if Chunked transfer encoding was. """ - return 'chunked' in self.environ.get( - 'HTTP_TRANSFER_ENCODING', '').lower() - - #: An alias for :attr:`query`. - GET = query - - @DictProperty('environ', 'bottle.request.post', read_only=True) - def POST(self): - """ The values of :attr:`forms` and :attr:`files` combined into a single - :class:`FormsDict`. Values are either strings (form values) or - instances of :class:`FileUpload`. - """ - post = FormsDict() - content_type = self.environ.get('CONTENT_TYPE', '') - content_type, options = _parse_http_header(content_type)[0] - # We default to application/x-www-form-urlencoded for everything that - # is not multipart and take the fast path (also: 3.1 workaround) - if not content_type.startswith('multipart/'): - body = tonat(self._get_body_string(self.MEMFILE_MAX), 'latin1') - for key, value in _parse_qsl(body): - post[key] = value - return post - - post.recode_unicode = False - charset = options.get("charset", "utf8") - boundary = options.get("boundary") - if not boundary: - raise MultipartError("Invalid content type header, missing boundary") - parser = _MultipartParser(self.body, boundary, self.content_length, - mem_limit=self.MEMFILE_MAX, memfile_limit=self.MEMFILE_MAX, - charset=charset) - - for part in parser.parse(): - if not part.filename and part.is_buffered(): - post[part.name] = tonat(part.value, 'utf8') - else: - post[part.name] = FileUpload(part.file, part.name, - part.filename, part.headerlist) - - return post - - @property - def url(self): - """ The full request URI including hostname and scheme. If your app - lives behind a reverse proxy or load balancer and you get confusing - results, make sure that the ``X-Forwarded-Host`` header is set - correctly. """ - return self.urlparts.geturl() - - @DictProperty('environ', 'bottle.request.urlparts', read_only=True) - def urlparts(self): - """ The :attr:`url` string as an :class:`urlparse.SplitResult` tuple. - The tuple contains (scheme, host, path, query_string and fragment), - but the fragment is always empty because it is not visible to the - server. """ - env = self.environ - http = env.get('HTTP_X_FORWARDED_PROTO') \ - or env.get('wsgi.url_scheme', 'http') - host = env.get('HTTP_X_FORWARDED_HOST') or env.get('HTTP_HOST') - if not host: - # HTTP 1.1 requires a Host-header. This is for HTTP/1.0 clients. - host = env.get('SERVER_NAME', '127.0.0.1') - port = env.get('SERVER_PORT') - if port and port != ('80' if http == 'http' else '443'): - host += ':' + port - path = urlquote(self.fullpath) - return UrlSplitResult(http, host, path, env.get('QUERY_STRING'), '') - - @property - def fullpath(self): - """ Request path including :attr:`script_name` (if present). """ - return urljoin(self.script_name, self.path.lstrip('/')) - - @property - def query_string(self): - """ The raw :attr:`query` part of the URL (everything in between ``?`` - and ``#``) as a string. """ - return self.environ.get('QUERY_STRING', '') - - @property - def script_name(self): - """ The initial portion of the URL's `path` that was removed by a higher - level (server or routing middleware) before the application was - called. This script path is returned with leading and tailing - slashes. """ - script_name = self.environ.get('SCRIPT_NAME', '').strip('/') - return '/' + script_name + '/' if script_name else '/' - - def path_shift(self, shift=1): - """ Shift path segments from :attr:`path` to :attr:`script_name` and - vice versa. - - :param shift: The number of path segments to shift. May be negative - to change the shift direction. (default: 1) - """ - script, path = path_shift(self.environ.get('SCRIPT_NAME', '/'), self.path, shift) - self['SCRIPT_NAME'], self['PATH_INFO'] = script, path - - @property - def content_length(self): - """ The request body length as an integer. The client is responsible to - set this header. Otherwise, the real length of the body is unknown - and -1 is returned. In this case, :attr:`body` will be empty. """ - return int(self.environ.get('CONTENT_LENGTH') or -1) - - @property - def content_type(self): - """ The Content-Type header as a lowercase-string (default: empty). """ - return self.environ.get('CONTENT_TYPE', '').lower() - - @property - def is_xhr(self): - """ True if the request was triggered by a XMLHttpRequest. This only - works with JavaScript libraries that support the `X-Requested-With` - header (most of the popular libraries do). """ - requested_with = self.environ.get('HTTP_X_REQUESTED_WITH', '') - return requested_with.lower() == 'xmlhttprequest' - - @property - def is_ajax(self): - """ Alias for :attr:`is_xhr`. "Ajax" is not the right term. """ - return self.is_xhr - - @property - def auth(self): - """ HTTP authentication data as a (user, password) tuple. This - implementation currently supports basic (not digest) authentication - only. If the authentication happened at a higher level (e.g. in the - front web-server or a middleware), the password field is None, but - the user field is looked up from the ``REMOTE_USER`` environ - variable. On any errors, None is returned. """ - basic = parse_auth(self.environ.get('HTTP_AUTHORIZATION', '')) - if basic: return basic - ruser = self.environ.get('REMOTE_USER') - if ruser: return (ruser, None) - return None - - @property - def remote_route(self): - """ A list of all IPs that were involved in this request, starting with - the client IP and followed by zero or more proxies. This does only - work if all proxies support the ```X-Forwarded-For`` header. Note - that this information can be forged by malicious clients. """ - proxy = self.environ.get('HTTP_X_FORWARDED_FOR') - if proxy: return [ip.strip() for ip in proxy.split(',')] - remote = self.environ.get('REMOTE_ADDR') - return [remote] if remote else [] - - @property - def remote_addr(self): - """ The client IP as a string. Note that this information can be forged - by malicious clients. """ - route = self.remote_route - return route[0] if route else None - - def copy(self): - """ Return a new :class:`Request` with a shallow :attr:`environ` copy. """ - return Request(self.environ.copy()) - - def get(self, value, default=None): - return self.environ.get(value, default) - - def __getitem__(self, key): - return self.environ[key] - - def __delitem__(self, key): - self[key] = "" - del (self.environ[key]) - - def __iter__(self): - return iter(self.environ) - - def __len__(self): - return len(self.environ) - - def keys(self): - return self.environ.keys() - - def __setitem__(self, key, value): - """ Change an environ value and clear all caches that depend on it. """ - - if self.environ.get('bottle.request.readonly'): - raise KeyError('The environ dictionary is read-only.') - - self.environ[key] = value - todelete = () - - if key == 'wsgi.input': - todelete = ('body', 'forms', 'files', 'params', 'post', 'json') - elif key == 'QUERY_STRING': - todelete = ('query', 'params') - elif key.startswith('HTTP_'): - todelete = ('headers', 'cookies') - - for key in todelete: - self.environ.pop('bottle.request.' + key, None) - - def __repr__(self): - return '<%s: %s %s>' % (self.__class__.__name__, self.method, self.url) - - def __getattr__(self, name): - """ Search in self.environ for additional user defined attributes. """ - try: - var = self.environ['bottle.request.ext.%s' % name] - return var.__get__(self) if hasattr(var, '__get__') else var - except KeyError: - raise AttributeError('Attribute %r not defined.' % name) - - def __setattr__(self, name, value): - """ Define new attributes that are local to the bound request environment. """ - if name == 'environ': return object.__setattr__(self, name, value) - key = 'bottle.request.ext.%s' % name - if hasattr(self, name): - raise AttributeError("Attribute already defined: %s" % name) - self.environ[key] = value - - def __delattr__(self, name): - try: - del self.environ['bottle.request.ext.%s' % name] - except KeyError: - raise AttributeError("Attribute not defined: %s" % name) - - -def _hkey(key): - if '\n' in key or '\r' in key or '\0' in key: - raise ValueError("Header names must not contain control characters: %r" % key) - return key.title().replace('_', '-') - - -def _hval(value): - value = tonat(value) - if '\n' in value or '\r' in value or '\0' in value: - raise ValueError("Header value must not contain control characters: %r" % value) - return value - - -class HeaderProperty(object): - def __init__(self, name, reader=None, writer=None, default=''): - self.name, self.default = name, default - self.reader, self.writer = reader, writer - self.__doc__ = 'Current value of the %r header.' % name.title() - - def __get__(self, obj, _): - if obj is None: return self - value = obj.get_header(self.name, self.default) - return self.reader(value) if self.reader else value - - def __set__(self, obj, value): - obj[self.name] = self.writer(value) if self.writer else value - - def __delete__(self, obj): - del obj[self.name] - - -class BaseResponse(object): - """ Storage class for a response body as well as headers and cookies. - - This class does support dict-like case-insensitive item-access to - headers, but is NOT a dict. Most notably, iterating over a response - yields parts of the body and not the headers. - """ - - default_status = 200 - default_content_type = 'text/html; charset=UTF-8' - - # Header denylist for specific response codes - # (rfc2616 section 10.2.3 and 10.3.5) - bad_headers = { - 204: frozenset(('Content-Type', 'Content-Length')), - 304: frozenset(('Allow', 'Content-Encoding', 'Content-Language', - 'Content-Length', 'Content-Range', 'Content-Type', - 'Content-Md5', 'Last-Modified')) - } - - def __init__(self, body='', status=None, headers=None, **more_headers): - """ Create a new response object. - - :param body: The response body as one of the supported types. - :param status: Either an HTTP status code (e.g. 200) or a status line - including the reason phrase (e.g. '200 OK'). - :param headers: A dictionary or a list of name-value pairs. - - Additional keyword arguments are added to the list of headers. - Underscores in the header name are replaced with dashes. - """ - self._cookies = None - self._headers = {} - self.body = body - self.status = status or self.default_status - if headers: - if isinstance(headers, dict): - headers = headers.items() - for name, value in headers: - self.add_header(name, value) - if more_headers: - for name, value in more_headers.items(): - self.add_header(name, value) - - def copy(self, cls=None): - """ Returns a copy of self. """ - cls = cls or BaseResponse - assert issubclass(cls, BaseResponse) - copy = cls() - copy.status = self.status - copy._headers = dict((k, v[:]) for (k, v) in self._headers.items()) - if self._cookies: - cookies = copy._cookies = SimpleCookie() - for k,v in self._cookies.items(): - cookies[k] = v.value - cookies[k].update(v) # also copy cookie attributes - return copy - - def __iter__(self): - return iter(self.body) - - def close(self): - if hasattr(self.body, 'close'): - self.body.close() - - @property - def status_line(self): - """ The HTTP status line as a string (e.g. ``404 Not Found``).""" - return self._status_line - - @property - def status_code(self): - """ The HTTP status code as an integer (e.g. 404).""" - return self._status_code - - def _set_status(self, status): - if isinstance(status, int): - code, status = status, _HTTP_STATUS_LINES.get(status) - elif ' ' in status: - if '\n' in status or '\r' in status or '\0' in status: - raise ValueError('Status line must not include control chars.') - status = status.strip() - code = int(status.split()[0]) - else: - raise ValueError('String status line without a reason phrase.') - if not 100 <= code <= 999: - raise ValueError('Status code out of range.') - self._status_code = code - self._status_line = str(status or ('%d Unknown' % code)) - - def _get_status(self): - return self._status_line - - status = property( - _get_status, _set_status, None, - ''' A writeable property to change the HTTP response status. It accepts - either a numeric code (100-999) or a string with a custom reason - phrase (e.g. "404 Brain not found"). Both :data:`status_line` and - :data:`status_code` are updated accordingly. The return value is - always a status string. ''') - del _get_status, _set_status - - @property - def headers(self): - """ An instance of :class:`HeaderDict`, a case-insensitive dict-like - view on the response headers. """ - hdict = HeaderDict() - hdict.dict = self._headers - return hdict - - def __contains__(self, name): - return _hkey(name) in self._headers - - def __delitem__(self, name): - del self._headers[_hkey(name)] - - def __getitem__(self, name): - return self._headers[_hkey(name)][-1] - - def __setitem__(self, name, value): - self._headers[_hkey(name)] = [_hval(value)] - - def get_header(self, name, default=None): - """ Return the value of a previously defined header. If there is no - header with that name, return a default value. """ - return self._headers.get(_hkey(name), [default])[-1] - - def set_header(self, name, value): - """ Create a new response header, replacing any previously defined - headers with the same name. """ - self._headers[_hkey(name)] = [_hval(value)] - - def add_header(self, name, value): - """ Add an additional response header, not removing duplicates. """ - self._headers.setdefault(_hkey(name), []).append(_hval(value)) - - def iter_headers(self): - """ Yield (header, value) tuples, skipping headers that are not - allowed with the current response status code. """ - return self.headerlist - - def _wsgi_status_line(self): - """ WSGI conform status line (latin1-encodeable) """ - if py3k: - return self._status_line.encode('utf8').decode('latin1') - return self._status_line - - @property - def headerlist(self): - """ WSGI conform list of (header, value) tuples. """ - out = [] - headers = list(self._headers.items()) - if 'Content-Type' not in self._headers: - headers.append(('Content-Type', [self.default_content_type])) - if self._status_code in self.bad_headers: - bad_headers = self.bad_headers[self._status_code] - headers = [h for h in headers if h[0] not in bad_headers] - out += [(name, val) for (name, vals) in headers for val in vals] - if self._cookies: - for c in self._cookies.values(): - out.append(('Set-Cookie', _hval(c.OutputString()))) - if py3k: - out = [(k, v.encode('utf8').decode('latin1')) for (k, v) in out] - return out - - content_type = HeaderProperty('Content-Type') - content_length = HeaderProperty('Content-Length', reader=int, default=-1) - expires = HeaderProperty( - 'Expires', - reader=lambda x: datetime.fromtimestamp(parse_date(x), UTC), - writer=lambda x: http_date(x)) - - @property - def charset(self, default='UTF-8'): - """ Return the charset specified in the content-type header (default: utf8). """ - if 'charset=' in self.content_type: - return self.content_type.split('charset=')[-1].split(';')[0].strip() - return default - - def set_cookie(self, name, value, secret=None, digestmod=hashlib.sha256, **options): - """ Create a new cookie or replace an old one. If the `secret` parameter is - set, create a `Signed Cookie` (described below). - - :param name: the name of the cookie. - :param value: the value of the cookie. - :param secret: a signature key required for signed cookies. - - Additionally, this method accepts all RFC 2109 attributes that are - supported by :class:`cookie.Morsel`, including: - - :param maxage: maximum age in seconds. (default: None) - :param expires: a datetime object or UNIX timestamp. (default: None) - :param domain: the domain that is allowed to read the cookie. - (default: current domain) - :param path: limits the cookie to a given path (default: current path) - :param secure: limit the cookie to HTTPS connections (default: off). - :param httponly: prevents client-side javascript to read this cookie - (default: off, requires Python 2.6 or newer). - :param samesite: Control or disable third-party use for this cookie. - Possible values: `lax`, `strict` or `none` (default). - - If neither `expires` nor `maxage` is set (default), the cookie will - expire at the end of the browser session (as soon as the browser - window is closed). - - Signed cookies may store any pickle-able object and are - cryptographically signed to prevent manipulation. Keep in mind that - cookies are limited to 4kb in most browsers. - - Warning: Pickle is a potentially dangerous format. If an attacker - gains access to the secret key, he could forge cookies that execute - code on server side if unpickled. Using pickle is discouraged and - support for it will be removed in later versions of bottle. - - Warning: Signed cookies are not encrypted (the client can still see - the content) and not copy-protected (the client can restore an old - cookie). The main intention is to make pickling and unpickling - save, not to store secret information at client side. - """ - if not self._cookies: - self._cookies = SimpleCookie() - - # Monkey-patch Cookie lib to support 'SameSite' parameter - # https://tools.ietf.org/html/draft-west-first-party-cookies-07#section-4.1 - if py < (3, 8, 0): - Morsel._reserved.setdefault('samesite', 'SameSite') - - if secret: - if not isinstance(value, basestring): - depr(0, 13, "Pickling of arbitrary objects into cookies is " - "deprecated.", "Only store strings in cookies. " - "JSON strings are fine, too.") - encoded = base64.b64encode(pickle.dumps([name, value], -1)) - sig = base64.b64encode(hmac.new(tob(secret), encoded, - digestmod=digestmod).digest()) - value = touni(tob('!') + sig + tob('?') + encoded) - elif not isinstance(value, basestring): - raise TypeError('Secret key required for non-string cookies.') - - # Cookie size plus options must not exceed 4kb. - if len(name) + len(value) > 3800: - raise ValueError('Content does not fit into a cookie.') - - self._cookies[name] = value - - for key, value in options.items(): - if key in ('max_age', 'maxage'): # 'maxage' variant added in 0.13 - key = 'max-age' - if isinstance(value, timedelta): - value = value.seconds + value.days * 24 * 3600 - if key == 'expires': - value = http_date(value) - if key in ('same_site', 'samesite'): # 'samesite' variant added in 0.13 - key, value = 'samesite', (value or "none").lower() - if value not in ('lax', 'strict', 'none'): - raise CookieError("Invalid value for SameSite") - if key in ('secure', 'httponly') and not value: - continue - self._cookies[name][key] = value - - def delete_cookie(self, key, **kwargs): - """ Delete a cookie. Be sure to use the same `domain` and `path` - settings as used to create the cookie. """ - kwargs['max_age'] = -1 - kwargs['expires'] = 0 - self.set_cookie(key, '', **kwargs) - - def __repr__(self): - out = '' - for name, value in self.headerlist: - out += '%s: %s\n' % (name.title(), value.strip()) - return out - - -def _local_property(): - ls = threading.local() - - def fget(_): - try: - return ls.var - except AttributeError: - raise RuntimeError("Request context not initialized.") - - def fset(_, value): - ls.var = value - - def fdel(_): - del ls.var - - return property(fget, fset, fdel, 'Thread-local property') - - -class LocalRequest(BaseRequest): - """ A thread-local subclass of :class:`BaseRequest` with a different - set of attributes for each thread. There is usually only one global - instance of this class (:data:`request`). If accessed during a - request/response cycle, this instance always refers to the *current* - request (even on a multithreaded server). """ - bind = BaseRequest.__init__ - environ = _local_property() - - -class LocalResponse(BaseResponse): - """ A thread-local subclass of :class:`BaseResponse` with a different - set of attributes for each thread. There is usually only one global - instance of this class (:data:`response`). Its attributes are used - to build the HTTP response at the end of the request/response cycle. - """ - bind = BaseResponse.__init__ - _status_line = _local_property() - _status_code = _local_property() - _cookies = _local_property() - _headers = _local_property() - body = _local_property() - - -Request = BaseRequest -Response = BaseResponse - - -class HTTPResponse(Response, BottleException): - """ A subclass of :class:`Response` that can be raised or returned from request - handlers to short-curcuit request processing and override changes made to the - global :data:`request` object. This bypasses error handlers, even if the status - code indicates an error. Return or raise :class:`HTTPError` to trigger error - handlers. - """ - - def __init__(self, body='', status=None, headers=None, **more_headers): - super(HTTPResponse, self).__init__(body, status, headers, **more_headers) - - def apply(self, other): - """ Copy the state of this response to a different :class:`Response` object. """ - other._status_code = self._status_code - other._status_line = self._status_line - other._headers = self._headers - other._cookies = self._cookies - other.body = self.body - - -class HTTPError(HTTPResponse): - """ A subclass of :class:`HTTPResponse` that triggers error handlers. """ - - default_status = 500 - - def __init__(self, - status=None, - body=None, - exception=None, - traceback=None, **more_headers): - self.exception = exception - self.traceback = traceback - super(HTTPError, self).__init__(body, status, **more_headers) - -############################################################################### -# Plugins ###################################################################### -############################################################################### - - -class PluginError(BottleException): - pass - - -class JSONPlugin(object): - name = 'json' - api = 2 - - def __init__(self, json_dumps=json_dumps): - self.json_dumps = json_dumps - - def setup(self, app): - app.config._define('json.enable', default=True, validate=bool, - help="Enable or disable automatic dict->json filter.") - app.config._define('json.ascii', default=False, validate=bool, - help="Use only 7-bit ASCII characters in output.") - app.config._define('json.indent', default=True, validate=bool, - help="Add whitespace to make json more readable.") - app.config._define('json.dump_func', default=None, - help="If defined, use this function to transform" - " dict into json. The other options no longer" - " apply.") - - def apply(self, callback, route): - dumps = self.json_dumps - if not self.json_dumps: return callback - - @functools.wraps(callback) - def wrapper(*a, **ka): - try: - rv = callback(*a, **ka) - except HTTPResponse as resp: - rv = resp - - if isinstance(rv, dict): - #Attempt to serialize, raises exception on failure - json_response = dumps(rv) - #Set content type only if serialization successful - response.content_type = 'application/json' - return json_response - elif isinstance(rv, HTTPResponse) and isinstance(rv.body, dict): - rv.body = dumps(rv.body) - rv.content_type = 'application/json' - return rv - - return wrapper - - -class TemplatePlugin(object): - """ This plugin applies the :func:`view` decorator to all routes with a - `template` config parameter. If the parameter is a tuple, the second - element must be a dict with additional options (e.g. `template_engine`) - or default variables for the template. """ - name = 'template' - api = 2 - - def setup(self, app): - app.tpl = self - - def apply(self, callback, route): - conf = route.config.get('template') - if isinstance(conf, (tuple, list)) and len(conf) == 2: - return view(conf[0], **conf[1])(callback) - elif isinstance(conf, str): - return view(conf)(callback) - else: - return callback - - -#: Not a plugin, but part of the plugin API. TODO: Find a better place. -class _ImportRedirect(object): - def __init__(self, name, impmask): - """ Create a virtual package that redirects imports (see PEP 302). """ - self.name = name - self.impmask = impmask - self.module = sys.modules.setdefault(name, new_module(name)) - self.module.__dict__.update({ - '__file__': __file__, - '__path__': [], - '__all__': [], - '__loader__': self - }) - sys.meta_path.append(self) - - def find_spec(self, fullname, path, target=None): - if '.' not in fullname: return - if fullname.rsplit('.', 1)[0] != self.name: return - from importlib.util import spec_from_loader - return spec_from_loader(fullname, self) - - def find_module(self, fullname, path=None): - if '.' not in fullname: return - if fullname.rsplit('.', 1)[0] != self.name: return - return self - - def create_module(self, spec): - return self.load_module(spec.name) - - def exec_module(self, module): - pass # This probably breaks importlib.reload() :/ - - def load_module(self, fullname): - if fullname in sys.modules: return sys.modules[fullname] - modname = fullname.rsplit('.', 1)[1] - realname = self.impmask % modname - __import__(realname) - module = sys.modules[fullname] = sys.modules[realname] - setattr(self.module, modname, module) - module.__loader__ = self - return module - -############################################################################### -# Common Utilities ############################################################# -############################################################################### - - -class MultiDict(DictMixin): - """ This dict stores multiple values per key, but behaves exactly like a - normal dict in that it returns only the newest value for any given key. - There are special methods available to access the full list of values. - """ - - def __init__(self, *a, **k): - self.dict = dict((k, [v]) for (k, v) in dict(*a, **k).items()) - - def __len__(self): - return len(self.dict) - - def __iter__(self): - return iter(self.dict) - - def __contains__(self, key): - return key in self.dict - - def __delitem__(self, key): - del self.dict[key] - - def __getitem__(self, key): - return self.dict[key][-1] - - def __setitem__(self, key, value): - self.append(key, value) - - def keys(self): - return self.dict.keys() - - if py3k: - - def values(self): - return (v[-1] for v in self.dict.values()) - - def items(self): - return ((k, v[-1]) for k, v in self.dict.items()) - - def allitems(self): - return ((k, v) for k, vl in self.dict.items() for v in vl) - - iterkeys = keys - itervalues = values - iteritems = items - iterallitems = allitems - - else: - - def values(self): - return [v[-1] for v in self.dict.values()] - - def items(self): - return [(k, v[-1]) for k, v in self.dict.items()] - - def iterkeys(self): - return self.dict.iterkeys() - - def itervalues(self): - return (v[-1] for v in self.dict.itervalues()) - - def iteritems(self): - return ((k, v[-1]) for k, v in self.dict.iteritems()) - - def iterallitems(self): - return ((k, v) for k, vl in self.dict.iteritems() for v in vl) - - def allitems(self): - return [(k, v) for k, vl in self.dict.iteritems() for v in vl] - - def get(self, key, default=None, index=-1, type=None): - """ Return the most recent value for a key. - - :param default: The default value to be returned if the key is not - present or the type conversion fails. - :param index: An index for the list of available values. - :param type: If defined, this callable is used to cast the value - into a specific type. Exception are suppressed and result in - the default value to be returned. - """ - try: - val = self.dict[key][index] - return type(val) if type else val - except Exception: - pass - return default - - def append(self, key, value): - """ Add a new value to the list of values for this key. """ - self.dict.setdefault(key, []).append(value) - - def replace(self, key, value): - """ Replace the list of values with a single value. """ - self.dict[key] = [value] - - def getall(self, key): - """ Return a (possibly empty) list of values for a key. """ - return self.dict.get(key) or [] - - #: Aliases for WTForms to mimic other multi-dict APIs (Django) - getone = get - getlist = getall - - -class FormsDict(MultiDict): - """ This :class:`MultiDict` subclass is used to store request form data. - Additionally to the normal dict-like item access methods (which return - unmodified data as native strings), this container also supports - attribute-like access to its values. Attributes are automatically de- - or recoded to match :attr:`input_encoding` (default: 'utf8'). Missing - attributes default to an empty string. """ - - #: Encoding used for attribute values. - input_encoding = 'utf8' - #: If true (default), unicode strings are first encoded with `latin1` - #: and then decoded to match :attr:`input_encoding`. - recode_unicode = True - - def _fix(self, s, encoding=None): - if isinstance(s, unicode) and self.recode_unicode: # Python 3 WSGI - return s.encode('latin1').decode(encoding or self.input_encoding) - elif isinstance(s, bytes): # Python 2 WSGI - return s.decode(encoding or self.input_encoding) - else: - return s - - def decode(self, encoding=None): - """ Returns a copy with all keys and values de- or recoded to match - :attr:`input_encoding`. Some libraries (e.g. WTForms) want a - unicode dictionary. """ - copy = FormsDict() - enc = copy.input_encoding = encoding or self.input_encoding - copy.recode_unicode = False - for key, value in self.allitems(): - copy.append(self._fix(key, enc), self._fix(value, enc)) - return copy - - def getunicode(self, name, default=None, encoding=None): - """ Return the value as a unicode string, or the default. """ - try: - return self._fix(self[name], encoding) - except (UnicodeError, KeyError): - return default - - def __getattr__(self, name, default=unicode()): - # Without this guard, pickle generates a cryptic TypeError: - if name.startswith('__') and name.endswith('__'): - return super(FormsDict, self).__getattr__(name) - return self.getunicode(name, default=default) - -class HeaderDict(MultiDict): - """ A case-insensitive version of :class:`MultiDict` that defaults to - replace the old value instead of appending it. """ - - def __init__(self, *a, **ka): - self.dict = {} - if a or ka: self.update(*a, **ka) - - def __contains__(self, key): - return _hkey(key) in self.dict - - def __delitem__(self, key): - del self.dict[_hkey(key)] - - def __getitem__(self, key): - return self.dict[_hkey(key)][-1] - - def __setitem__(self, key, value): - self.dict[_hkey(key)] = [_hval(value)] - - def append(self, key, value): - self.dict.setdefault(_hkey(key), []).append(_hval(value)) - - def replace(self, key, value): - self.dict[_hkey(key)] = [_hval(value)] - - def getall(self, key): - return self.dict.get(_hkey(key)) or [] - - def get(self, key, default=None, index=-1): - return MultiDict.get(self, _hkey(key), default, index) - - def filter(self, names): - for name in (_hkey(n) for n in names): - if name in self.dict: - del self.dict[name] - - -class WSGIHeaderDict(DictMixin): - """ This dict-like class wraps a WSGI environ dict and provides convenient - access to HTTP_* fields. Keys and values are native strings - (2.x bytes or 3.x unicode) and keys are case-insensitive. If the WSGI - environment contains non-native string values, these are de- or encoded - using a lossless 'latin1' character set. - - The API will remain stable even on changes to the relevant PEPs. - Currently PEP 333, 444 and 3333 are supported. (PEP 444 is the only one - that uses non-native strings.) - """ - #: List of keys that do not have a ``HTTP_`` prefix. - cgikeys = ('CONTENT_TYPE', 'CONTENT_LENGTH') - - def __init__(self, environ): - self.environ = environ - - def _ekey(self, key): - """ Translate header field name to CGI/WSGI environ key. """ - key = key.replace('-', '_').upper() - if key in self.cgikeys: - return key - return 'HTTP_' + key - - def raw(self, key, default=None): - """ Return the header value as is (may be bytes or unicode). """ - return self.environ.get(self._ekey(key), default) - - def __getitem__(self, key): - val = self.environ[self._ekey(key)] - if py3k: - if isinstance(val, unicode): - val = val.encode('latin1').decode('utf8') - else: - val = val.decode('utf8') - return val - - def __setitem__(self, key, value): - raise TypeError("%s is read-only." % self.__class__) - - def __delitem__(self, key): - raise TypeError("%s is read-only." % self.__class__) - - def __iter__(self): - for key in self.environ: - if key[:5] == 'HTTP_': - yield _hkey(key[5:]) - elif key in self.cgikeys: - yield _hkey(key) - - def keys(self): - return [x for x in self] - - def __len__(self): - return len(self.keys()) - - def __contains__(self, key): - return self._ekey(key) in self.environ - -_UNSET = object() - -class ConfigDict(dict): - """ A dict-like configuration storage with additional support for - namespaces, validators, meta-data and overlays. - - This dict-like class is heavily optimized for read access. - Read-only methods and item access should be as fast as a native dict. - """ - - __slots__ = ('_meta', '_change_listener', '_overlays', '_virtual_keys', '_source', '__weakref__') - - def __init__(self): - self._meta = {} - self._change_listener = [] - #: Weak references of overlays that need to be kept in sync. - self._overlays = [] - #: Config that is the source for this overlay. - self._source = None - #: Keys of values copied from the source (values we do not own) - self._virtual_keys = set() - - def load_module(self, name, squash=True): - """Load values from a Python module. - - Import a python module by name and add all upper-case module-level - variables to this config dict. - - :param name: Module name to import and load. - :param squash: If true (default), nested dicts are assumed to - represent namespaces and flattened (see :meth:`load_dict`). - """ - config_obj = load(name) - obj = {key: getattr(config_obj, key) - for key in dir(config_obj) if key.isupper()} - - if squash: - self.load_dict(obj) - else: - self.update(obj) - return self - - def load_config(self, filename, **options): - """ Load values from ``*.ini`` style config files using configparser. - - INI style sections (e.g. ``[section]``) are used as namespace for - all keys within that section. Both section and key names may contain - dots as namespace separators and are converted to lower-case. - - The special sections ``[bottle]`` and ``[ROOT]`` refer to the root - namespace and the ``[DEFAULT]`` section defines default values for all - other sections. - - :param filename: The path of a config file, or a list of paths. - :param options: All keyword parameters are passed to the underlying - :class:`python:configparser.ConfigParser` constructor call. - - """ - options.setdefault('allow_no_value', True) - if py3k: - options.setdefault('interpolation', - configparser.ExtendedInterpolation()) - conf = configparser.ConfigParser(**options) - conf.read(filename) - for section in conf.sections(): - for key in conf.options(section): - value = conf.get(section, key) - if section not in ('bottle', 'ROOT'): - key = section + '.' + key - self[key.lower()] = value - return self - - def load_dict(self, source, namespace=''): - """ Load values from a dictionary structure. Nesting can be used to - represent namespaces. - - >>> c = ConfigDict() - >>> c.load_dict({'some': {'namespace': {'key': 'value'} } }) - {'some.namespace.key': 'value'} - """ - for key, value in source.items(): - if isinstance(key, basestring): - nskey = (namespace + '.' + key).strip('.') - if isinstance(value, dict): - self.load_dict(value, namespace=nskey) - else: - self[nskey] = value - else: - raise TypeError('Key has type %r (not a string)' % type(key)) - return self - - def update(self, *a, **ka): - """ If the first parameter is a string, all keys are prefixed with this - namespace. Apart from that it works just as the usual dict.update(). - - >>> c = ConfigDict() - >>> c.update('some.namespace', key='value') - """ - prefix = '' - if a and isinstance(a[0], basestring): - prefix = a[0].strip('.') + '.' - a = a[1:] - for key, value in dict(*a, **ka).items(): - self[prefix + key] = value - - def setdefault(self, key, value=None): - if key not in self: - self[key] = value - return self[key] - - def __setitem__(self, key, value): - if not isinstance(key, basestring): - raise TypeError('Key has type %r (not a string)' % type(key)) - - self._virtual_keys.discard(key) - - value = self.meta_get(key, 'filter', lambda x: x)(value) - if key in self and self[key] is value: - return - - self._on_change(key, value) - dict.__setitem__(self, key, value) - - for overlay in self._iter_overlays(): - overlay._set_virtual(key, value) - - def __delitem__(self, key): - if key not in self: - raise KeyError(key) - if key in self._virtual_keys: - raise KeyError("Virtual keys cannot be deleted: %s" % key) - - if self._source and key in self._source: - # Not virtual, but present in source -> Restore virtual value - dict.__delitem__(self, key) - self._set_virtual(key, self._source[key]) - else: # not virtual, not present in source. This is OUR value - self._on_change(key, None) - dict.__delitem__(self, key) - for overlay in self._iter_overlays(): - overlay._delete_virtual(key) - - def _set_virtual(self, key, value): - """ Recursively set or update virtual keys. """ - if key in self and key not in self._virtual_keys: - return # Do nothing for non-virtual keys. - - self._virtual_keys.add(key) - if key in self and self[key] is not value: - self._on_change(key, value) - dict.__setitem__(self, key, value) - for overlay in self._iter_overlays(): - overlay._set_virtual(key, value) - - def _delete_virtual(self, key): - """ Recursively delete virtual entry. """ - if key not in self._virtual_keys: - return # Do nothing for non-virtual keys. - - if key in self: - self._on_change(key, None) - dict.__delitem__(self, key) - self._virtual_keys.discard(key) - for overlay in self._iter_overlays(): - overlay._delete_virtual(key) - - def _on_change(self, key, value): - for cb in self._change_listener: - if cb(self, key, value): - return True - - def _add_change_listener(self, func): - self._change_listener.append(func) - return func - - def meta_get(self, key, metafield, default=None): - """ Return the value of a meta field for a key. """ - return self._meta.get(key, {}).get(metafield, default) - - def meta_set(self, key, metafield, value): - """ Set the meta field for a key to a new value. - - Meta-fields are shared between all members of an overlay tree. - """ - self._meta.setdefault(key, {})[metafield] = value - - def meta_list(self, key): - """ Return an iterable of meta field names defined for a key. """ - return self._meta.get(key, {}).keys() - - def _define(self, key, default=_UNSET, help=_UNSET, validate=_UNSET): - """ (Unstable) Shortcut for plugins to define own config parameters. """ - if default is not _UNSET: - self.setdefault(key, default) - if help is not _UNSET: - self.meta_set(key, 'help', help) - if validate is not _UNSET: - self.meta_set(key, 'validate', validate) - - def _iter_overlays(self): - for ref in self._overlays: - overlay = ref() - if overlay is not None: - yield overlay - - def _make_overlay(self): - """ (Unstable) Create a new overlay that acts like a chained map: Values - missing in the overlay are copied from the source map. Both maps - share the same meta entries. - - Entries that were copied from the source are called 'virtual'. You - can not delete virtual keys, but overwrite them, which turns them - into non-virtual entries. Setting keys on an overlay never affects - its source, but may affect any number of child overlays. - - Other than collections.ChainMap or most other implementations, this - approach does not resolve missing keys on demand, but instead - actively copies all values from the source to the overlay and keeps - track of virtual and non-virtual keys internally. This removes any - lookup-overhead. Read-access is as fast as a build-in dict for both - virtual and non-virtual keys. - - Changes are propagated recursively and depth-first. A failing - on-change handler in an overlay stops the propagation of virtual - values and may result in an partly updated tree. Take extra care - here and make sure that on-change handlers never fail. - - Used by Route.config - """ - # Cleanup dead references - self._overlays[:] = [ref for ref in self._overlays if ref() is not None] - - overlay = ConfigDict() - overlay._meta = self._meta - overlay._source = self - self._overlays.append(weakref.ref(overlay)) - for key in self: - overlay._set_virtual(key, self[key]) - return overlay - - - - -class AppStack(list): - """ A stack-like list. Calling it returns the head of the stack. """ - - def __call__(self): - """ Return the current default application. """ - return self.default - - def push(self, value=None): - """ Add a new :class:`Bottle` instance to the stack """ - if not isinstance(value, Bottle): - value = Bottle() - self.append(value) - return value - new_app = push - - @property - def default(self): - try: - return self[-1] - except IndexError: - return self.push() - - -class WSGIFileWrapper(object): - def __init__(self, fp, buffer_size=1024 * 64): - self.fp, self.buffer_size = fp, buffer_size - for attr in 'fileno', 'close', 'read', 'readlines', 'tell', 'seek': - if hasattr(fp, attr): setattr(self, attr, getattr(fp, attr)) - - def __iter__(self): - buff, read = self.buffer_size, self.read - part = read(buff) - while part: - yield part - part = read(buff) - - -class _closeiter(object): - """ This only exists to be able to attach a .close method to iterators that - do not support attribute assignment (most of itertools). """ - - def __init__(self, iterator, close=None): - self.iterator = iterator - self.close_callbacks = makelist(close) - - def __iter__(self): - return iter(self.iterator) - - def close(self): - for func in self.close_callbacks: - func() - - -class ResourceManager(object): - """ This class manages a list of search paths and helps to find and open - application-bound resources (files). - - :param base: default value for :meth:`add_path` calls. - :param opener: callable used to open resources. - :param cachemode: controls which lookups are cached. One of 'all', - 'found' or 'none'. - """ - - def __init__(self, base='./', opener=open, cachemode='all'): - self.opener = opener - self.base = base - self.cachemode = cachemode - - #: A list of search paths. See :meth:`add_path` for details. - self.path = [] - #: A cache for resolved paths. ``res.cache.clear()`` clears the cache. - self.cache = {} - - def add_path(self, path, base=None, index=None, create=False): - """ Add a new path to the list of search paths. Return False if the - path does not exist. - - :param path: The new search path. Relative paths are turned into - an absolute and normalized form. If the path looks like a file - (not ending in `/`), the filename is stripped off. - :param base: Path used to absolutize relative search paths. - Defaults to :attr:`base` which defaults to ``os.getcwd()``. - :param index: Position within the list of search paths. Defaults - to last index (appends to the list). - - The `base` parameter makes it easy to reference files installed - along with a python module or package:: - - res.add_path('./resources/', __file__) - """ - base = os.path.abspath(os.path.dirname(base or self.base)) - path = os.path.abspath(os.path.join(base, os.path.dirname(path))) - path += os.sep - if path in self.path: - self.path.remove(path) - if create and not os.path.isdir(path): - os.makedirs(path) - if index is None: - self.path.append(path) - else: - self.path.insert(index, path) - self.cache.clear() - return os.path.exists(path) - - def __iter__(self): - """ Iterate over all existing files in all registered paths. """ - search = self.path[:] - while search: - path = search.pop() - if not os.path.isdir(path): continue - for name in os.listdir(path): - full = os.path.join(path, name) - if os.path.isdir(full): search.append(full) - else: yield full - - def lookup(self, name): - """ Search for a resource and return an absolute file path, or `None`. - - The :attr:`path` list is searched in order. The first match is - returned. Symlinks are followed. The result is cached to speed up - future lookups. """ - if name not in self.cache or DEBUG: - for path in self.path: - fpath = os.path.join(path, name) - if os.path.isfile(fpath): - if self.cachemode in ('all', 'found'): - self.cache[name] = fpath - return fpath - if self.cachemode == 'all': - self.cache[name] = None - return self.cache[name] - - def open(self, name, mode='r', *args, **kwargs): - """ Find a resource and return a file object, or raise IOError. """ - fname = self.lookup(name) - if not fname: raise IOError("Resource %r not found." % name) - return self.opener(fname, mode=mode, *args, **kwargs) - - -class FileUpload(object): - def __init__(self, fileobj, name, filename, headers=None): - """ Wrapper for a single file uploaded via ``multipart/form-data``. """ - #: Open file(-like) object (BytesIO buffer or temporary file) - self.file = fileobj - #: Name of the upload form field - self.name = name - #: Raw filename as sent by the client (may contain unsafe characters) - self.raw_filename = filename - #: A :class:`HeaderDict` with additional headers (e.g. content-type) - self.headers = HeaderDict(headers) if headers else HeaderDict() - - content_type = HeaderProperty('Content-Type') - content_length = HeaderProperty('Content-Length', reader=int, default=-1) - - def get_header(self, name, default=None): - """ Return the value of a header within the multipart part. """ - return self.headers.get(name, default) - - @cached_property - def filename(self): - """ Name of the file on the client file system, but normalized to ensure - file system compatibility. An empty filename is returned as 'empty'. - - Only ASCII letters, digits, dashes, underscores and dots are - allowed in the final filename. Accents are removed, if possible. - Whitespace is replaced by a single dash. Leading or tailing dots - or dashes are removed. The filename is limited to 255 characters. - """ - fname = self.raw_filename - if not isinstance(fname, unicode): - fname = fname.decode('utf8', 'ignore') - fname = normalize('NFKD', fname) - fname = fname.encode('ASCII', 'ignore').decode('ASCII') - fname = os.path.basename(fname.replace('\\', os.path.sep)) - fname = re.sub(r'[^a-zA-Z0-9-_.\s]', '', fname).strip() - fname = re.sub(r'[-\s]+', '-', fname).strip('.-') - return fname[:255] or 'empty' - - def _copy_file(self, fp, chunk_size=2 ** 16): - read, write, offset = self.file.read, fp.write, self.file.tell() - while 1: - buf = read(chunk_size) - if not buf: break - write(buf) - self.file.seek(offset) - - def save(self, destination, overwrite=False, chunk_size=2 ** 16): - """ Save file to disk or copy its content to an open file(-like) object. - If *destination* is a directory, :attr:`filename` is added to the - path. Existing files are not overwritten by default (IOError). - - :param destination: File path, directory or file(-like) object. - :param overwrite: If True, replace existing files. (default: False) - :param chunk_size: Bytes to read at a time. (default: 64kb) - """ - if isinstance(destination, basestring): # Except file-likes here - if os.path.isdir(destination): - destination = os.path.join(destination, self.filename) - if not overwrite and os.path.exists(destination): - raise IOError('File exists.') - with open(destination, 'wb') as fp: - self._copy_file(fp, chunk_size) - else: - self._copy_file(destination, chunk_size) - -############################################################################### -# Application Helper ########################################################### -############################################################################### - - -def abort(code=500, text='Unknown Error.'): - """ Aborts execution and causes a HTTP error. """ - raise HTTPError(code, text) - - -def redirect(url, code=None): - """ Aborts execution and causes a 303 or 302 redirect, depending on - the HTTP protocol version. """ - if not code: - code = 303 if request.get('SERVER_PROTOCOL') == "HTTP/1.1" else 302 - res = response.copy(cls=HTTPResponse) - res.status = code - res.body = "" - res.set_header('Location', urljoin(request.url, url)) - raise res - - -def _rangeiter(fp, offset, limit, bufsize=1024 * 1024): - """ Yield chunks from a range in a file. """ - fp.seek(offset) - while limit > 0: - part = fp.read(min(limit, bufsize)) - if not part: - break - limit -= len(part) - yield part - - -def static_file(filename, root, - mimetype=True, - download=False, - charset='UTF-8', - etag=None, - headers=None): - """ Open a file in a safe way and return an instance of :exc:`HTTPResponse` - that can be sent back to the client. - - :param filename: Name or path of the file to send, relative to ``root``. - :param root: Root path for file lookups. Should be an absolute directory - path. - :param mimetype: Provide the content-type header (default: guess from - file extension) - :param download: If True, ask the browser to open a `Save as...` dialog - instead of opening the file with the associated program. You can - specify a custom filename as a string. If not specified, the - original filename is used (default: False). - :param charset: The charset for files with a ``text/*`` mime-type. - (default: UTF-8) - :param etag: Provide a pre-computed ETag header. If set to ``False``, - ETag handling is disabled. (default: auto-generate ETag header) - :param headers: Additional headers dict to add to the response. - - While checking user input is always a good idea, this function provides - additional protection against malicious ``filename`` parameters from - breaking out of the ``root`` directory and leaking sensitive information - to an attacker. - - Read-protected files or files outside of the ``root`` directory are - answered with ``403 Access Denied``. Missing files result in a - ``404 Not Found`` response. Conditional requests (``If-Modified-Since``, - ``If-None-Match``) are answered with ``304 Not Modified`` whenever - possible. ``HEAD`` and ``Range`` requests (used by download managers to - check or continue partial downloads) are also handled automatically. - """ - - root = os.path.join(os.path.abspath(root), '') - filename = os.path.abspath(os.path.join(root, filename.strip('/\\'))) - headers = headers.copy() if headers else {} - getenv = request.environ.get - - if not filename.startswith(root): - return HTTPError(403, "Access denied.") - if not os.path.exists(filename) or not os.path.isfile(filename): - return HTTPError(404, "File does not exist.") - if not os.access(filename, os.R_OK): - return HTTPError(403, "You do not have permission to access this file.") - - if mimetype is True: - name = download if isinstance(download, str) else filename - mimetype, encoding = mimetypes.guess_type(name) - if encoding == 'gzip': - mimetype = 'application/gzip' - elif encoding: # e.g. bzip2 -> application/x-bzip2 - mimetype = 'application/x-' + encoding - - if charset and mimetype and 'charset=' not in mimetype \ - and (mimetype[:5] == 'text/' or mimetype == 'application/javascript'): - mimetype += '; charset=%s' % charset - - if mimetype: - headers['Content-Type'] = mimetype - - if download is True: - download = os.path.basename(filename) - - if download: - download = download.replace('"','') - headers['Content-Disposition'] = 'attachment; filename="%s"' % download - - stats = os.stat(filename) - headers['Content-Length'] = clen = stats.st_size - headers['Last-Modified'] = email.utils.formatdate(stats.st_mtime, usegmt=True) - headers['Date'] = email.utils.formatdate(time.time(), usegmt=True) - - if etag is None: - etag = '%d:%d:%d:%d:%s' % (stats.st_dev, stats.st_ino, stats.st_mtime, - clen, filename) - etag = hashlib.sha1(tob(etag)).hexdigest() - - if etag: - headers['ETag'] = etag - check = getenv('HTTP_IF_NONE_MATCH') - if check and check == etag: - return HTTPResponse(status=304, **headers) - - ims = getenv('HTTP_IF_MODIFIED_SINCE') - if ims: - ims = parse_date(ims.split(";")[0].strip()) - if ims is not None and ims >= int(stats.st_mtime): - return HTTPResponse(status=304, **headers) - - body = '' if request.method == 'HEAD' else open(filename, 'rb') - - headers["Accept-Ranges"] = "bytes" - range_header = getenv('HTTP_RANGE') - if range_header: - ranges = list(parse_range_header(range_header, clen)) - if not ranges: - return HTTPError(416, "Requested Range Not Satisfiable") - offset, end = ranges[0] - rlen = end - offset - headers["Content-Range"] = "bytes %d-%d/%d" % (offset, end - 1, clen) - headers["Content-Length"] = str(rlen) - if body: body = _closeiter(_rangeiter(body, offset, rlen), body.close) - return HTTPResponse(body, status=206, **headers) - return HTTPResponse(body, **headers) - -############################################################################### -# HTTP Utilities and MISC (TODO) ############################################### -############################################################################### - - -def debug(mode=True): - """ Change the debug level. - There is only one debug level supported at the moment.""" - global DEBUG - if mode: warnings.simplefilter('default') - DEBUG = bool(mode) - - -def http_date(value): - if isinstance(value, basestring): - return value - if isinstance(value, datetime): - # aware datetime.datetime is converted to UTC time - # naive datetime.datetime is treated as UTC time - value = value.utctimetuple() - elif isinstance(value, datedate): - # datetime.date is naive, and is treated as UTC time - value = value.timetuple() - if not isinstance(value, (int, float)): - # convert struct_time in UTC to UNIX timestamp - value = calendar.timegm(value) - return email.utils.formatdate(value, usegmt=True) - - -def parse_date(ims): - """ Parse rfc1123, rfc850 and asctime timestamps and return UTC epoch. """ - try: - ts = email.utils.parsedate_tz(ims) - return calendar.timegm(ts[:8] + (0, )) - (ts[9] or 0) - except (TypeError, ValueError, IndexError, OverflowError): - return None - - -def parse_auth(header): - """ Parse rfc2617 HTTP authentication header string (basic) and return (user,pass) tuple or None""" - try: - method, data = header.split(None, 1) - if method.lower() == 'basic': - user, pwd = touni(base64.b64decode(tob(data))).split(':', 1) - return user, pwd - except (KeyError, ValueError): - return None - - -def parse_range_header(header, maxlen=0): - """ Yield (start, end) ranges parsed from a HTTP Range header. Skip - unsatisfiable ranges. The end index is non-inclusive.""" - if not header or header[:6] != 'bytes=': return - ranges = [r.split('-', 1) for r in header[6:].split(',') if '-' in r] - for start, end in ranges: - try: - if not start: # bytes=-100 -> last 100 bytes - start, end = max(0, maxlen - int(end)), maxlen - elif not end: # bytes=100- -> all but the first 99 bytes - start, end = int(start), maxlen - else: # bytes=100-200 -> bytes 100-200 (inclusive) - start, end = int(start), min(int(end) + 1, maxlen) - if 0 <= start < end <= maxlen: - yield start, end - except ValueError: - pass - - -#: Header tokenizer used by _parse_http_header() -_hsplit = re.compile('(?:(?:"((?:[^"\\\\]|\\\\.)*)")|([^;,=]+))([;,=]?)').findall - -def _parse_http_header(h): - """ Parses a typical multi-valued and parametrised HTTP header (e.g. Accept headers) and returns a list of values - and parameters. For non-standard or broken input, this implementation may return partial results. - :param h: A header string (e.g. ``text/html,text/plain;q=0.9,*/*;q=0.8``) - :return: List of (value, params) tuples. The second element is a (possibly empty) dict. - """ - values = [] - if '"' not in h: # INFO: Fast path without regexp (~2x faster) - for value in h.split(','): - parts = value.split(';') - values.append((parts[0].strip(), {})) - for attr in parts[1:]: - name, value = attr.split('=', 1) - values[-1][1][name.strip().lower()] = value.strip() - else: - lop, key, attrs = ',', None, {} - for quoted, plain, tok in _hsplit(h): - value = plain.strip() if plain else quoted.replace('\\"', '"') - if lop == ',': - attrs = {} - values.append((value, attrs)) - elif lop == ';': - if tok == '=': - key = value - else: - attrs[value.strip().lower()] = '' - elif lop == '=' and key: - attrs[key.strip().lower()] = value - key = None - lop = tok - return values - - -def _parse_qsl(qs): - r = [] - for pair in qs.split('&'): - if not pair: continue - nv = pair.split('=', 1) - if len(nv) != 2: nv.append('') - key = urlunquote(nv[0].replace('+', ' ')) - value = urlunquote(nv[1].replace('+', ' ')) - r.append((key, value)) - return r - - -def _lscmp(a, b): - """ Compares two strings in a cryptographically safe way: - Runtime is not affected by length of common prefix. """ - return not sum(0 if x == y else 1 - for x, y in zip(a, b)) and len(a) == len(b) - - -def cookie_encode(data, key, digestmod=None): - """ Encode and sign a pickle-able object. Return a (byte) string """ - depr(0, 13, "cookie_encode() will be removed soon.", - "Do not use this API directly.") - digestmod = digestmod or hashlib.sha256 - msg = base64.b64encode(pickle.dumps(data, -1)) - sig = base64.b64encode(hmac.new(tob(key), msg, digestmod=digestmod).digest()) - return tob('!') + sig + tob('?') + msg - - -def cookie_decode(data, key, digestmod=None): - """ Verify and decode an encoded string. Return an object or None.""" - depr(0, 13, "cookie_decode() will be removed soon.", - "Do not use this API directly.") - data = tob(data) - if cookie_is_encoded(data): - sig, msg = data.split(tob('?'), 1) - digestmod = digestmod or hashlib.sha256 - hashed = hmac.new(tob(key), msg, digestmod=digestmod).digest() - if _lscmp(sig[1:], base64.b64encode(hashed)): - return pickle.loads(base64.b64decode(msg)) - return None - - -def cookie_is_encoded(data): - """ Return True if the argument looks like a encoded cookie.""" - depr(0, 13, "cookie_is_encoded() will be removed soon.", - "Do not use this API directly.") - return bool(data.startswith(tob('!')) and tob('?') in data) - - -def html_escape(string): - """ Escape HTML special characters ``&<>`` and quotes ``'"``. """ - return string.replace('&', '&').replace('<', '<').replace('>', '>')\ - .replace('"', '"').replace("'", ''') - - -def html_quote(string): - """ Escape and quote a string to be used as an HTTP attribute.""" - return '"%s"' % html_escape(string).replace('\n', ' ')\ - .replace('\r', ' ').replace('\t', ' ') - - -def yieldroutes(func): - """ Return a generator for routes that match the signature (name, args) - of the func parameter. This may yield more than one route if the function - takes optional keyword arguments. The output is best described by example:: - - a() -> '/a' - b(x, y) -> '/b//' - c(x, y=5) -> '/c/' and '/c//' - d(x=5, y=6) -> '/d' and '/d/' and '/d//' - """ - path = '/' + func.__name__.replace('__', '/').lstrip('/') - spec = getargspec(func) - argc = len(spec[0]) - len(spec[3] or []) - path += ('/<%s>' * argc) % tuple(spec[0][:argc]) - yield path - for arg in spec[0][argc:]: - path += '/<%s>' % arg - yield path - - -def path_shift(script_name, path_info, shift=1): - """ Shift path fragments from PATH_INFO to SCRIPT_NAME and vice versa. - - :return: The modified paths. - :param script_name: The SCRIPT_NAME path. - :param script_name: The PATH_INFO path. - :param shift: The number of path fragments to shift. May be negative to - change the shift direction. (default: 1) - """ - if shift == 0: return script_name, path_info - pathlist = path_info.strip('/').split('/') - scriptlist = script_name.strip('/').split('/') - if pathlist and pathlist[0] == '': pathlist = [] - if scriptlist and scriptlist[0] == '': scriptlist = [] - if 0 < shift <= len(pathlist): - moved = pathlist[:shift] - scriptlist = scriptlist + moved - pathlist = pathlist[shift:] - elif 0 > shift >= -len(scriptlist): - moved = scriptlist[shift:] - pathlist = moved + pathlist - scriptlist = scriptlist[:shift] - else: - empty = 'SCRIPT_NAME' if shift < 0 else 'PATH_INFO' - raise AssertionError("Cannot shift. Nothing left from %s" % empty) - new_script_name = '/' + '/'.join(scriptlist) - new_path_info = '/' + '/'.join(pathlist) - if path_info.endswith('/') and pathlist: new_path_info += '/' - return new_script_name, new_path_info - - -def auth_basic(check, realm="private", text="Access denied"): - """ Callback decorator to require HTTP auth (basic). - TODO: Add route(check_auth=...) parameter. """ - - def decorator(func): - - @functools.wraps(func) - def wrapper(*a, **ka): - user, password = request.auth or (None, None) - if user is None or not check(user, password): - err = HTTPError(401, text) - err.add_header('WWW-Authenticate', 'Basic realm="%s"' % realm) - return err - return func(*a, **ka) - - return wrapper - - return decorator - -# Shortcuts for common Bottle methods. -# They all refer to the current default application. - - -def make_default_app_wrapper(name): - """ Return a callable that relays calls to the current default app. """ - - @functools.wraps(getattr(Bottle, name)) - def wrapper(*a, **ka): - return getattr(app(), name)(*a, **ka) - - return wrapper - - -route = make_default_app_wrapper('route') -get = make_default_app_wrapper('get') -post = make_default_app_wrapper('post') -put = make_default_app_wrapper('put') -delete = make_default_app_wrapper('delete') -patch = make_default_app_wrapper('patch') -error = make_default_app_wrapper('error') -mount = make_default_app_wrapper('mount') -hook = make_default_app_wrapper('hook') -install = make_default_app_wrapper('install') -uninstall = make_default_app_wrapper('uninstall') -url = make_default_app_wrapper('get_url') - - -############################################################################### -# Multipart Handling ########################################################### -############################################################################### -# cgi.FieldStorage was deprecated in Python 3.11 and removed in 3.13 -# This implementation is based on https://github.com/defnull/multipart/ - - -class MultipartError(HTTPError): - def __init__(self, msg): - HTTPError.__init__(self, 400, "MultipartError: " + msg) - - -class _MultipartParser(object): - def __init__( - self, - stream, - boundary, - content_length=-1, - disk_limit=2 ** 30, - mem_limit=2 ** 20, - memfile_limit=2 ** 18, - buffer_size=2 ** 16, - charset="latin1", - ): - self.stream = stream - self.boundary = boundary - self.content_length = content_length - self.disk_limit = disk_limit - self.memfile_limit = memfile_limit - self.mem_limit = min(mem_limit, self.disk_limit) - self.buffer_size = min(buffer_size, self.mem_limit) - self.charset = charset - - if not boundary: - raise MultipartError("No boundary.") - - if self.buffer_size - 6 < len(boundary): # "--boundary--\r\n" - raise MultipartError("Boundary does not fit into buffer_size.") - - def _lineiter(self): - """ Iterate over a binary file-like object (crlf terminated) line by - line. Each line is returned as a (line, crlf) tuple. Lines larger - than buffer_size are split into chunks where all but the last chunk - has an empty string instead of crlf. Maximum chunk size is twice the - buffer size. - """ - - read = self.stream.read - maxread, maxbuf = self.content_length, self.buffer_size - partial = b"" # Contains the last (partial) line - - while True: - chunk = read(maxbuf if maxread < 0 else min(maxbuf, maxread)) - maxread -= len(chunk) - if not chunk: - if partial: - yield partial, b'' - break - - if partial: - chunk = partial + chunk - - scanpos = 0 - while True: - i = chunk.find(b'\r\n', scanpos) - if i >= 0: - yield chunk[scanpos:i], b'\r\n' - scanpos = i + 2 - else: # CRLF not found - partial = chunk[scanpos:] if scanpos else chunk - break - - if len(partial) > maxbuf: - yield partial[:-1], b"" - partial = partial[-1:] - - def parse(self): - """ Return a MultiPart iterator. Can only be called once. """ - - lines, line = self._lineiter(), "" - separator = b"--" + tob(self.boundary) - terminator = separator + b"--" - mem_used, disk_used = 0, 0 # Track used resources to prevent DoS - is_tail = False # True if the last line was incomplete (cutted) - - # Consume first boundary. Ignore any preamble, as required by RFC - # 2046, section 5.1.1. - for line, nl in lines: - if line in (separator, terminator): - break - else: - raise MultipartError("Stream does not contain boundary") - - # First line is termainating boundary -> empty multipart stream - if line == terminator: - for _ in lines: - raise MultipartError("Found data after empty multipart stream") - return - - part_options = { - "buffer_size": self.buffer_size, - "memfile_limit": self.memfile_limit, - "charset": self.charset, - } - part = _MultipartPart(**part_options) - - for line, nl in lines: - if not is_tail and (line == separator or line == terminator): - part.finish() - if part.is_buffered(): - mem_used += part.size - else: - disk_used += part.size - yield part - if line == terminator: - break - part = _MultipartPart(**part_options) - else: - is_tail = not nl # The next line continues this one - try: - part.feed(line, nl) - if part.is_buffered(): - if part.size + mem_used > self.mem_limit: - raise MultipartError("Memory limit reached.") - elif part.size + disk_used > self.disk_limit: - raise MultipartError("Disk limit reached.") - except MultipartError: - part.close() - raise - else: - part.close() - - if line != terminator: - raise MultipartError("Unexpected end of multipart stream.") - - -class _MultipartPart(object): - def __init__(self, buffer_size=2 ** 16, memfile_limit=2 ** 18, charset="latin1"): - self.headerlist = [] - self.headers = None - self.file = False - self.size = 0 - self._buf = b"" - self.disposition = None - self.name = None - self.filename = None - self.content_type = None - self.charset = charset - self.memfile_limit = memfile_limit - self.buffer_size = buffer_size - - def feed(self, line, nl=""): - if self.file: - return self.write_body(line, nl) - return self.write_header(line, nl) - - def write_header(self, line, nl): - line = line.decode(self.charset) - - if not nl: - raise MultipartError("Unexpected end of line in header.") - - if not line.strip(): # blank line -> end of header segment - self.finish_header() - elif line[0] in " \t" and self.headerlist: - name, value = self.headerlist.pop() - self.headerlist.append((name, value + line.strip())) - else: - if ":" not in line: - raise MultipartError("Syntax error in header: No colon.") - - name, value = line.split(":", 1) - self.headerlist.append((name.strip(), value.strip())) - - def write_body(self, line, nl): - if not line and not nl: - return # This does not even flush the buffer - - self.size += len(line) + len(self._buf) - self.file.write(self._buf + line) - self._buf = nl - - if self.content_length > 0 and self.size > self.content_length: - raise MultipartError("Size of body exceeds Content-Length header.") - - if self.size > self.memfile_limit and isinstance(self.file, BytesIO): - self.file, old = NamedTemporaryFile(mode="w+b"), self.file - old.seek(0) - - copied, maxcopy, chunksize = 0, self.size, self.buffer_size - read, write = old.read, self.file.write - while copied < maxcopy: - chunk = read(min(chunksize, maxcopy - copied)) - write(chunk) - copied += len(chunk) - - def finish_header(self): - self.file = BytesIO() - self.headers = HeaderDict(self.headerlist) - content_disposition = self.headers.get("Content-Disposition") - content_type = self.headers.get("Content-Type") - - if not content_disposition: - raise MultipartError("Content-Disposition header is missing.") - - self.disposition, self.options = _parse_http_header(content_disposition)[0] - self.name = self.options.get("name") - if "filename" in self.options: - self.filename = self.options.get("filename") - if self.filename[1:3] == ":\\" or self.filename[:2] == "\\\\": - self.filename = self.filename.split("\\")[-1] # ie6 bug - - self.content_type, options = _parse_http_header(content_type)[0] if content_type else (None, {}) - self.charset = options.get("charset") or self.charset - - self.content_length = int(self.headers.get("Content-Length", "-1")) - - def finish(self): - if not self.file: - raise MultipartError("Incomplete part: Header section not closed.") - self.file.seek(0) - - def is_buffered(self): - """ Return true if the data is fully buffered in memory.""" - return isinstance(self.file, BytesIO) - - @property - def value(self): - """ Data decoded with the specified charset """ - - return self.raw.decode(self.charset) - - @property - def raw(self): - """ Data without decoding """ - pos = self.file.tell() - self.file.seek(0) - - try: - return self.file.read() - finally: - self.file.seek(pos) - - def close(self): - if self.file: - self.file.close() - self.file = False - -############################################################################### -# Server Adapter ############################################################### -############################################################################### - -# Before you edit or add a server adapter, please read: -# - https://github.com/bottlepy/bottle/pull/647#issuecomment-60152870 -# - https://github.com/bottlepy/bottle/pull/865#issuecomment-242795341 - -class ServerAdapter(object): - quiet = False - - def __init__(self, host='127.0.0.1', port=8080, **options): - self.options = options - self.host = host - self.port = int(port) - - def run(self, handler): # pragma: no cover - pass - - def __repr__(self): - args = ', '.join('%s=%s' % (k, repr(v)) - for k, v in self.options.items()) - return "%s(%s)" % (self.__class__.__name__, args) - - -class CGIServer(ServerAdapter): - quiet = True - - def run(self, handler): # pragma: no cover - from wsgiref.handlers import CGIHandler - - def fixed_environ(environ, start_response): - environ.setdefault('PATH_INFO', '') - return handler(environ, start_response) - - CGIHandler().run(fixed_environ) - - -class FlupFCGIServer(ServerAdapter): - def run(self, handler): # pragma: no cover - import flup.server.fcgi - self.options.setdefault('bindAddress', (self.host, self.port)) - flup.server.fcgi.WSGIServer(handler, **self.options).run() - - -class WSGIRefServer(ServerAdapter): - def run(self, app): # pragma: no cover - from wsgiref.simple_server import make_server - from wsgiref.simple_server import WSGIRequestHandler, WSGIServer - import socket - - class FixedHandler(WSGIRequestHandler): - def address_string(self): # Prevent reverse DNS lookups please. - return self.client_address[0] - - def log_request(*args, **kw): - if not self.quiet: - return WSGIRequestHandler.log_request(*args, **kw) - - handler_cls = self.options.get('handler_class', FixedHandler) - server_cls = self.options.get('server_class', WSGIServer) - - if ':' in self.host: # Fix wsgiref for IPv6 addresses. - if getattr(server_cls, 'address_family') == socket.AF_INET: - - class server_cls(server_cls): - address_family = socket.AF_INET6 - - self.srv = make_server(self.host, self.port, app, server_cls, - handler_cls) - self.port = self.srv.server_port # update port actual port (0 means random) - try: - self.srv.serve_forever() - except KeyboardInterrupt: - self.srv.server_close() # Prevent ResourceWarning: unclosed socket - raise - - -class CherryPyServer(ServerAdapter): - def run(self, handler): # pragma: no cover - depr(0, 13, "The wsgi server part of cherrypy was split into a new " - "project called 'cheroot'.", "Use the 'cheroot' server " - "adapter instead of cherrypy.") - from cherrypy import wsgiserver # This will fail for CherryPy >= 9 - - self.options['bind_addr'] = (self.host, self.port) - self.options['wsgi_app'] = handler - - certfile = self.options.get('certfile') - if certfile: - del self.options['certfile'] - keyfile = self.options.get('keyfile') - if keyfile: - del self.options['keyfile'] - - server = wsgiserver.CherryPyWSGIServer(**self.options) - if certfile: - server.ssl_certificate = certfile - if keyfile: - server.ssl_private_key = keyfile - - try: - server.start() - finally: - server.stop() - - -class CherootServer(ServerAdapter): - def run(self, handler): # pragma: no cover - from cheroot import wsgi - from cheroot.ssl import builtin - self.options['bind_addr'] = (self.host, self.port) - self.options['wsgi_app'] = handler - certfile = self.options.pop('certfile', None) - keyfile = self.options.pop('keyfile', None) - chainfile = self.options.pop('chainfile', None) - server = wsgi.Server(**self.options) - if certfile and keyfile: - server.ssl_adapter = builtin.BuiltinSSLAdapter( - certfile, keyfile, chainfile) - try: - server.start() - finally: - server.stop() - - -class WaitressServer(ServerAdapter): - def run(self, handler): - from waitress import serve - serve(handler, host=self.host, port=self.port, _quiet=self.quiet, **self.options) - - -class PasteServer(ServerAdapter): - def run(self, handler): # pragma: no cover - from paste import httpserver - from paste.translogger import TransLogger - handler = TransLogger(handler, setup_console_handler=(not self.quiet)) - httpserver.serve(handler, - host=self.host, - port=str(self.port), **self.options) - - -class MeinheldServer(ServerAdapter): - def run(self, handler): - from meinheld import server - server.listen((self.host, self.port)) - server.run(handler) - - -class FapwsServer(ServerAdapter): - """ Extremely fast webserver using libev. See https://github.com/william-os4y/fapws3 """ - - def run(self, handler): # pragma: no cover - depr(0, 13, "fapws3 is not maintained and support will be dropped.") - import fapws._evwsgi as evwsgi - from fapws import base, config - port = self.port - if float(config.SERVER_IDENT[-2:]) > 0.4: - # fapws3 silently changed its API in 0.5 - port = str(port) - evwsgi.start(self.host, port) - # fapws3 never releases the GIL. Complain upstream. I tried. No luck. - if 'BOTTLE_CHILD' in os.environ and not self.quiet: - _stderr("WARNING: Auto-reloading does not work with Fapws3.") - _stderr(" (Fapws3 breaks python thread support)") - evwsgi.set_base_module(base) - - def app(environ, start_response): - environ['wsgi.multiprocess'] = False - return handler(environ, start_response) - - evwsgi.wsgi_cb(('', app)) - evwsgi.run() - - -class TornadoServer(ServerAdapter): - """ The super hyped asynchronous server by facebook. Untested. """ - - def run(self, handler): # pragma: no cover - import tornado.wsgi, tornado.httpserver, tornado.ioloop - container = tornado.wsgi.WSGIContainer(handler) - server = tornado.httpserver.HTTPServer(container) - server.listen(port=self.port, address=self.host) - tornado.ioloop.IOLoop.instance().start() - - -class AppEngineServer(ServerAdapter): - """ Adapter for Google App Engine. """ - quiet = True - - def run(self, handler): - depr(0, 13, "AppEngineServer no longer required", - "Configure your application directly in your app.yaml") - from google.appengine.ext.webapp import util - # A main() function in the handler script enables 'App Caching'. - # Lets makes sure it is there. This _really_ improves performance. - module = sys.modules.get('__main__') - if module and not hasattr(module, 'main'): - module.main = lambda: util.run_wsgi_app(handler) - util.run_wsgi_app(handler) - - -class TwistedServer(ServerAdapter): - """ Untested. """ - - def run(self, handler): - from twisted.web import server, wsgi - from twisted.python.threadpool import ThreadPool - from twisted.internet import reactor - thread_pool = ThreadPool() - thread_pool.start() - reactor.addSystemEventTrigger('after', 'shutdown', thread_pool.stop) - factory = server.Site(wsgi.WSGIResource(reactor, thread_pool, handler)) - reactor.listenTCP(self.port, factory, interface=self.host) - if not reactor.running: - reactor.run() - - -class DieselServer(ServerAdapter): - """ Untested. """ - - def run(self, handler): - depr(0, 13, "Diesel is not tested or supported and will be removed.") - from diesel.protocols.wsgi import WSGIApplication - app = WSGIApplication(handler, port=self.port) - app.run() - - -class GeventServer(ServerAdapter): - """ Untested. Options: - - * See gevent.wsgi.WSGIServer() documentation for more options. - """ - - def run(self, handler): - from gevent import pywsgi, local - if not isinstance(threading.local(), local.local): - msg = "Bottle requires gevent.monkey.patch_all() (before import)" - raise RuntimeError(msg) - if self.quiet: - self.options['log'] = None - address = (self.host, self.port) - server = pywsgi.WSGIServer(address, handler, **self.options) - if 'BOTTLE_CHILD' in os.environ: - import signal - signal.signal(signal.SIGINT, lambda s, f: server.stop()) - server.serve_forever() - - -class GunicornServer(ServerAdapter): - """ Untested. See http://gunicorn.org/configure.html for options. """ - - def run(self, handler): - from gunicorn.app.base import BaseApplication - - if self.host.startswith("unix:"): - config = {'bind': self.host} - else: - config = {'bind': "%s:%d" % (self.host, self.port)} - - config.update(self.options) - - class GunicornApplication(BaseApplication): - def load_config(self): - for key, value in config.items(): - self.cfg.set(key, value) - - def load(self): - return handler - - GunicornApplication().run() - - -class EventletServer(ServerAdapter): - """ Untested. Options: - - * `backlog` adjust the eventlet backlog parameter which is the maximum - number of queued connections. Should be at least 1; the maximum - value is system-dependent. - * `family`: (default is 2) socket family, optional. See socket - documentation for available families. - """ - - def run(self, handler): - from eventlet import wsgi, listen, patcher - if not patcher.is_monkey_patched(os): - msg = "Bottle requires eventlet.monkey_patch() (before import)" - raise RuntimeError(msg) - socket_args = {} - for arg in ('backlog', 'family'): - try: - socket_args[arg] = self.options.pop(arg) - except KeyError: - pass - address = (self.host, self.port) - try: - wsgi.server(listen(address, **socket_args), handler, - log_output=(not self.quiet)) - except TypeError: - # Fallback, if we have old version of eventlet - wsgi.server(listen(address), handler) - - -class BjoernServer(ServerAdapter): - """ Fast server written in C: https://github.com/jonashaag/bjoern """ - - def run(self, handler): - from bjoern import run - run(handler, self.host, self.port, reuse_port=True) - -class AsyncioServerAdapter(ServerAdapter): - """ Extend ServerAdapter for adding custom event loop """ - def get_event_loop(self): - pass - -class AiohttpServer(AsyncioServerAdapter): - """ Asynchronous HTTP client/server framework for asyncio - https://pypi.python.org/pypi/aiohttp/ - https://pypi.org/project/aiohttp-wsgi/ - """ - - def get_event_loop(self): - import asyncio - return asyncio.new_event_loop() - - def run(self, handler): - import asyncio - from aiohttp_wsgi.wsgi import serve - self.loop = self.get_event_loop() - asyncio.set_event_loop(self.loop) - - if 'BOTTLE_CHILD' in os.environ: - import signal - signal.signal(signal.SIGINT, lambda s, f: self.loop.stop()) - - serve(handler, host=self.host, port=self.port) - - -class AiohttpUVLoopServer(AiohttpServer): - """uvloop - https://github.com/MagicStack/uvloop - """ - def get_event_loop(self): - import uvloop - return uvloop.new_event_loop() - -class AutoServer(ServerAdapter): - """ Untested. """ - adapters = [WaitressServer, PasteServer, TwistedServer, CherryPyServer, - CherootServer, WSGIRefServer] - - def run(self, handler): - for sa in self.adapters: - try: - return sa(self.host, self.port, **self.options).run(handler) - except ImportError: - pass - - -server_names = { - 'cgi': CGIServer, - 'flup': FlupFCGIServer, - 'wsgiref': WSGIRefServer, - 'waitress': WaitressServer, - 'cherrypy': CherryPyServer, - 'cheroot': CherootServer, - 'paste': PasteServer, - 'fapws3': FapwsServer, - 'tornado': TornadoServer, - 'gae': AppEngineServer, - 'twisted': TwistedServer, - 'diesel': DieselServer, - 'meinheld': MeinheldServer, - 'gunicorn': GunicornServer, - 'eventlet': EventletServer, - 'gevent': GeventServer, - 'bjoern': BjoernServer, - 'aiohttp': AiohttpServer, - 'uvloop': AiohttpUVLoopServer, - 'auto': AutoServer, -} - -############################################################################### -# Application Control ########################################################## -############################################################################### - - -def load(target, **namespace): - """ Import a module or fetch an object from a module. - - * ``package.module`` returns `module` as a module object. - * ``pack.mod:name`` returns the module variable `name` from `pack.mod`. - * ``pack.mod:func()`` calls `pack.mod.func()` and returns the result. - - The last form accepts not only function calls, but any type of - expression. Keyword arguments passed to this function are available as - local variables. Example: ``import_string('re:compile(x)', x='[a-z]')`` - """ - module, target = target.split(":", 1) if ':' in target else (target, None) - if module not in sys.modules: __import__(module) - if not target: return sys.modules[module] - if target.isalnum(): return getattr(sys.modules[module], target) - package_name = module.split('.')[0] - namespace[package_name] = sys.modules[package_name] - return eval('%s.%s' % (module, target), namespace) - - -def load_app(target): - """ Load a bottle application from a module and make sure that the import - does not affect the current default application, but returns a separate - application object. See :func:`load` for the target parameter. """ - global NORUN - NORUN, nr_old = True, NORUN - tmp = default_app.push() # Create a new "default application" - try: - rv = load(target) # Import the target module - return rv if callable(rv) else tmp - finally: - default_app.remove(tmp) # Remove the temporary added default application - NORUN = nr_old - - -_debug = debug - - -def run(app=None, - server='wsgiref', - host='127.0.0.1', - port=8080, - interval=1, - reloader=False, - quiet=False, - plugins=None, - debug=None, - config=None, **kargs): - """ Start a server instance. This method blocks until the server terminates. - - :param app: WSGI application or target string supported by - :func:`load_app`. (default: :func:`default_app`) - :param server: Server adapter to use. See :data:`server_names` keys - for valid names or pass a :class:`ServerAdapter` subclass. - (default: `wsgiref`) - :param host: Server address to bind to. Pass ``0.0.0.0`` to listens on - all interfaces including the external one. (default: 127.0.0.1) - :param port: Server port to bind to. Values below 1024 require root - privileges. (default: 8080) - :param reloader: Start auto-reloading server? (default: False) - :param interval: Auto-reloader interval in seconds (default: 1) - :param quiet: Suppress output to stdout and stderr? (default: False) - :param options: Options passed to the server adapter. - """ - if NORUN: return - if reloader and not os.environ.get('BOTTLE_CHILD'): - import subprocess - fd, lockfile = tempfile.mkstemp(prefix='bottle.', suffix='.lock') - environ = os.environ.copy() - environ['BOTTLE_CHILD'] = 'true' - environ['BOTTLE_LOCKFILE'] = lockfile - args = [sys.executable] + sys.argv - # If a package was loaded with `python -m`, then `sys.argv` needs to be - # restored to the original value, or imports might break. See #1336 - if getattr(sys.modules.get('__main__'), '__package__', None): - args[1:1] = ["-m", sys.modules['__main__'].__package__] - - try: - os.close(fd) # We never write to this file - while os.path.exists(lockfile): - p = subprocess.Popen(args, env=environ) - while p.poll() is None: - os.utime(lockfile, None) # Tell child we are still alive - time.sleep(interval) - if p.returncode == 3: # Child wants to be restarted - continue - sys.exit(p.returncode) - except KeyboardInterrupt: - pass - finally: - if os.path.exists(lockfile): - os.unlink(lockfile) - return - - try: - if debug is not None: _debug(debug) - app = app or default_app() - if isinstance(app, basestring): - app = load_app(app) - if not callable(app): - raise ValueError("Application is not callable: %r" % app) - - for plugin in plugins or []: - if isinstance(plugin, basestring): - plugin = load(plugin) - app.install(plugin) - - if config: - app.config.update(config) - - if server in server_names: - server = server_names.get(server) - if isinstance(server, basestring): - server = load(server) - if isinstance(server, type): - server = server(host=host, port=port, **kargs) - if not isinstance(server, ServerAdapter): - raise ValueError("Unknown or unsupported server: %r" % server) - - server.quiet = server.quiet or quiet - if not server.quiet: - _stderr("Bottle v%s server starting up (using %s)..." % - (__version__, repr(server))) - if server.host.startswith("unix:"): - _stderr("Listening on %s" % server.host) - else: - _stderr("Listening on http://%s:%d/" % - (server.host, server.port)) - _stderr("Hit Ctrl-C to quit.\n") - - if reloader: - lockfile = os.environ.get('BOTTLE_LOCKFILE') - bgcheck = FileCheckerThread(lockfile, interval) - with bgcheck: - server.run(app) - if bgcheck.status == 'reload': - sys.exit(3) - else: - server.run(app) - except KeyboardInterrupt: - pass - except (SystemExit, MemoryError): - raise - except: - if not reloader: raise - if not getattr(server, 'quiet', quiet): - print_exc() - time.sleep(interval) - sys.exit(3) - - -class FileCheckerThread(threading.Thread): - """ Interrupt main-thread as soon as a changed module file is detected, - the lockfile gets deleted or gets too old. """ - - def __init__(self, lockfile, interval): - threading.Thread.__init__(self) - self.daemon = True - self.lockfile, self.interval = lockfile, interval - #: Is one of 'reload', 'error' or 'exit' - self.status = None - - def run(self): - exists = os.path.exists - mtime = lambda p: os.stat(p).st_mtime - files = dict() - - for module in list(sys.modules.values()): - path = getattr(module, '__file__', '') or '' - if path[-4:] in ('.pyo', '.pyc'): path = path[:-1] - if path and exists(path): files[path] = mtime(path) - - while not self.status: - if not exists(self.lockfile)\ - or mtime(self.lockfile) < time.time() - self.interval - 5: - self.status = 'error' - thread.interrupt_main() - for path, lmtime in list(files.items()): - if not exists(path) or mtime(path) > lmtime: - self.status = 'reload' - thread.interrupt_main() - break - time.sleep(self.interval) - - def __enter__(self): - self.start() - - def __exit__(self, exc_type, *_): - if not self.status: self.status = 'exit' # silent exit - self.join() - return exc_type is not None and issubclass(exc_type, KeyboardInterrupt) - -############################################################################### -# Template Adapters ############################################################ -############################################################################### - - -class TemplateError(BottleException): - pass - - -class BaseTemplate(object): - """ Base class and minimal API for template adapters """ - extensions = ['tpl', 'html', 'thtml', 'stpl'] - settings = {} #used in prepare() - defaults = {} #used in render() - - def __init__(self, - source=None, - name=None, - lookup=None, - encoding='utf8', **settings): - """ Create a new template. - If the source parameter (str or buffer) is missing, the name argument - is used to guess a template filename. Subclasses can assume that - self.source and/or self.filename are set. Both are strings. - The lookup, encoding and settings parameters are stored as instance - variables. - The lookup parameter stores a list containing directory paths. - The encoding parameter should be used to decode byte strings or files. - The settings parameter contains a dict for engine-specific settings. - """ - self.name = name - self.source = source.read() if hasattr(source, 'read') else source - self.filename = source.filename if hasattr(source, 'filename') else None - self.lookup = [os.path.abspath(x) for x in lookup] if lookup else [] - self.encoding = encoding - self.settings = self.settings.copy() # Copy from class variable - self.settings.update(settings) # Apply - if not self.source and self.name: - self.filename = self.search(self.name, self.lookup) - if not self.filename: - raise TemplateError('Template %s not found.' % repr(name)) - if not self.source and not self.filename: - raise TemplateError('No template specified.') - self.prepare(**self.settings) - - @classmethod - def search(cls, name, lookup=None): - """ Search name in all directories specified in lookup. - First without, then with common extensions. Return first hit. """ - if not lookup: - raise depr(0, 12, "Empty template lookup path.", "Configure a template lookup path.") - - if os.path.isabs(name): - raise depr(0, 12, "Use of absolute path for template name.", - "Refer to templates with names or paths relative to the lookup path.") - - for spath in lookup: - spath = os.path.abspath(spath) + os.sep - fname = os.path.abspath(os.path.join(spath, name)) - if not fname.startswith(spath): continue - if os.path.isfile(fname): return fname - for ext in cls.extensions: - if os.path.isfile('%s.%s' % (fname, ext)): - return '%s.%s' % (fname, ext) - - @classmethod - def global_config(cls, key, *args): - """ This reads or sets the global settings stored in class.settings. """ - if args: - cls.settings = cls.settings.copy() # Make settings local to class - cls.settings[key] = args[0] - else: - return cls.settings[key] - - def prepare(self, **options): - """ Run preparations (parsing, caching, ...). - It should be possible to call this again to refresh a template or to - update settings. - """ - raise NotImplementedError - - def render(self, *args, **kwargs): - """ Render the template with the specified local variables and return - a single byte or unicode string. If it is a byte string, the encoding - must match self.encoding. This method must be thread-safe! - Local variables may be provided in dictionaries (args) - or directly, as keywords (kwargs). - """ - raise NotImplementedError - - -class MakoTemplate(BaseTemplate): - def prepare(self, **options): - from mako.template import Template - from mako.lookup import TemplateLookup - options.update({'input_encoding': self.encoding}) - options.setdefault('format_exceptions', bool(DEBUG)) - lookup = TemplateLookup(directories=self.lookup, **options) - if self.source: - self.tpl = Template(self.source, lookup=lookup, **options) - else: - self.tpl = Template(uri=self.name, - filename=self.filename, - lookup=lookup, **options) - - def render(self, *args, **kwargs): - for dictarg in args: - kwargs.update(dictarg) - _defaults = self.defaults.copy() - _defaults.update(kwargs) - return self.tpl.render(**_defaults) - - -class CheetahTemplate(BaseTemplate): - def prepare(self, **options): - from Cheetah.Template import Template - self.context = threading.local() - self.context.vars = {} - options['searchList'] = [self.context.vars] - if self.source: - self.tpl = Template(source=self.source, **options) - else: - self.tpl = Template(file=self.filename, **options) - - def render(self, *args, **kwargs): - for dictarg in args: - kwargs.update(dictarg) - self.context.vars.update(self.defaults) - self.context.vars.update(kwargs) - out = str(self.tpl) - self.context.vars.clear() - return out - - -class Jinja2Template(BaseTemplate): - def prepare(self, filters=None, tests=None, globals={}, **kwargs): - from jinja2 import Environment, FunctionLoader - self.env = Environment(loader=FunctionLoader(self.loader), **kwargs) - if filters: self.env.filters.update(filters) - if tests: self.env.tests.update(tests) - if globals: self.env.globals.update(globals) - if self.source: - self.tpl = self.env.from_string(self.source) - else: - self.tpl = self.env.get_template(self.name) - - def render(self, *args, **kwargs): - for dictarg in args: - kwargs.update(dictarg) - _defaults = self.defaults.copy() - _defaults.update(kwargs) - return self.tpl.render(**_defaults) - - def loader(self, name): - if name == self.filename: - fname = name - else: - fname = self.search(name, self.lookup) - if not fname: return - with open(fname, "rb") as f: - return (f.read().decode(self.encoding), fname, lambda: False) - - -class SimpleTemplate(BaseTemplate): - def prepare(self, - escape_func=html_escape, - noescape=False, - syntax=None, **ka): - self.cache = {} - enc = self.encoding - self._str = lambda x: touni(x, enc) - self._escape = lambda x: escape_func(touni(x, enc)) - self.syntax = syntax - if noescape: - self._str, self._escape = self._escape, self._str - - @cached_property - def co(self): - return compile(self.code, self.filename or '', 'exec') - - @cached_property - def code(self): - source = self.source - if not source: - with open(self.filename, 'rb') as f: - source = f.read() - try: - source, encoding = touni(source), 'utf8' - except UnicodeError: - raise depr(0, 11, 'Unsupported template encodings.', 'Use utf-8 for templates.') - parser = StplParser(source, encoding=encoding, syntax=self.syntax) - code = parser.translate() - self.encoding = parser.encoding - return code - - def _rebase(self, _env, _name=None, **kwargs): - _env['_rebase'] = (_name, kwargs) - - def _include(self, _env, _name=None, **kwargs): - env = _env.copy() - env.update(kwargs) - if _name not in self.cache: - self.cache[_name] = self.__class__(name=_name, lookup=self.lookup, syntax=self.syntax) - return self.cache[_name].execute(env['_stdout'], env) - - def execute(self, _stdout, kwargs): - env = self.defaults.copy() - env.update(kwargs) - env.update({ - '_stdout': _stdout, - '_printlist': _stdout.extend, - 'include': functools.partial(self._include, env), - 'rebase': functools.partial(self._rebase, env), - '_rebase': None, - '_str': self._str, - '_escape': self._escape, - 'get': env.get, - 'setdefault': env.setdefault, - 'defined': env.__contains__ - }) - exec(self.co, env) - if env.get('_rebase'): - subtpl, rargs = env.pop('_rebase') - rargs['base'] = ''.join(_stdout) #copy stdout - del _stdout[:] # clear stdout - return self._include(env, subtpl, **rargs) - return env - - def render(self, *args, **kwargs): - """ Render the template using keyword arguments as local variables. """ - env = {} - stdout = [] - for dictarg in args: - env.update(dictarg) - env.update(kwargs) - self.execute(stdout, env) - return ''.join(stdout) - - -class StplSyntaxError(TemplateError): - pass - - -class StplParser(object): - """ Parser for stpl templates. """ - _re_cache = {} #: Cache for compiled re patterns - - # This huge pile of voodoo magic splits python code into 8 different tokens. - # We use the verbose (?x) regex mode to make this more manageable - - _re_tok = r'''( - [urbURB]* - (?: ''(?!') - |""(?!") - |'{6} - |"{6} - |'(?:[^\\']|\\.)+?' - |"(?:[^\\"]|\\.)+?" - |'{3}(?:[^\\]|\\.|\n)+?'{3} - |"{3}(?:[^\\]|\\.|\n)+?"{3} - ) - )''' - - _re_inl = _re_tok.replace(r'|\n', '') # We re-use this string pattern later - - _re_tok += r''' - # 2: Comments (until end of line, but not the newline itself) - |(\#.*) - - # 3: Open and close (4) grouping tokens - |([\[\{\(]) - |([\]\}\)]) - - # 5,6: Keywords that start or continue a python block (only start of line) - |^([\ \t]*(?:if|for|while|with|try|def|class)\b) - |^([\ \t]*(?:elif|else|except|finally)\b) - - # 7: Our special 'end' keyword (but only if it stands alone) - |((?:^|;)[\ \t]*end[\ \t]*(?=(?:%(block_close)s[\ \t]*)?\r?$|;|\#)) - - # 8: A customizable end-of-code-block template token (only end of line) - |(%(block_close)s[\ \t]*(?=\r?$)) - - # 9: And finally, a single newline. The 10th token is 'everything else' - |(\r?\n) - ''' - - # Match the start tokens of code areas in a template - _re_split = r'''(?m)^[ \t]*(\\?)((%(line_start)s)|(%(block_start)s))''' - # Match inline statements (may contain python strings) - _re_inl = r'''%%(inline_start)s((?:%s|[^'"\n])*?)%%(inline_end)s''' % _re_inl - - # add the flag in front of the regexp to avoid Deprecation warning (see Issue #949) - # verbose and dot-matches-newline mode - _re_tok = '(?mx)' + _re_tok - _re_inl = '(?mx)' + _re_inl - - - default_syntax = '<% %> % {{ }}' - - def __init__(self, source, syntax=None, encoding='utf8'): - self.source, self.encoding = touni(source, encoding), encoding - self.set_syntax(syntax or self.default_syntax) - self.code_buffer, self.text_buffer = [], [] - self.lineno, self.offset = 1, 0 - self.indent, self.indent_mod = 0, 0 - self.paren_depth = 0 - - def get_syntax(self): - """ Tokens as a space separated string (default: <% %> % {{ }}) """ - return self._syntax - - def set_syntax(self, syntax): - self._syntax = syntax - self._tokens = syntax.split() - if syntax not in self._re_cache: - names = 'block_start block_close line_start inline_start inline_end' - etokens = map(re.escape, self._tokens) - pattern_vars = dict(zip(names.split(), etokens)) - patterns = (self._re_split, self._re_tok, self._re_inl) - patterns = [re.compile(p % pattern_vars) for p in patterns] - self._re_cache[syntax] = patterns - self.re_split, self.re_tok, self.re_inl = self._re_cache[syntax] - - syntax = property(get_syntax, set_syntax) - - def translate(self): - if self.offset: raise RuntimeError('Parser is a one time instance.') - while True: - m = self.re_split.search(self.source, pos=self.offset) - if m: - text = self.source[self.offset:m.start()] - self.text_buffer.append(text) - self.offset = m.end() - if m.group(1): # Escape syntax - line, sep, _ = self.source[self.offset:].partition('\n') - self.text_buffer.append(self.source[m.start():m.start(1)] + - m.group(2) + line + sep) - self.offset += len(line + sep) - continue - self.flush_text() - self.offset += self.read_code(self.source[self.offset:], - multiline=bool(m.group(4))) - else: - break - self.text_buffer.append(self.source[self.offset:]) - self.flush_text() - return ''.join(self.code_buffer) - - def read_code(self, pysource, multiline): - code_line, comment = '', '' - offset = 0 - while True: - m = self.re_tok.search(pysource, pos=offset) - if not m: - code_line += pysource[offset:] - offset = len(pysource) - self.write_code(code_line.strip(), comment) - break - code_line += pysource[offset:m.start()] - offset = m.end() - _str, _com, _po, _pc, _blk1, _blk2, _end, _cend, _nl = m.groups() - if self.paren_depth > 0 and (_blk1 or _blk2): # a if b else c - code_line += _blk1 or _blk2 - continue - if _str: # Python string - code_line += _str - elif _com: # Python comment (up to EOL) - comment = _com - if multiline and _com.strip().endswith(self._tokens[1]): - multiline = False # Allow end-of-block in comments - elif _po: # open parenthesis - self.paren_depth += 1 - code_line += _po - elif _pc: # close parenthesis - if self.paren_depth > 0: - # we could check for matching parentheses here, but it's - # easier to leave that to python - just check counts - self.paren_depth -= 1 - code_line += _pc - elif _blk1: # Start-block keyword (if/for/while/def/try/...) - code_line = _blk1 - self.indent += 1 - self.indent_mod -= 1 - elif _blk2: # Continue-block keyword (else/elif/except/...) - code_line = _blk2 - self.indent_mod -= 1 - elif _cend: # The end-code-block template token (usually '%>') - if multiline: multiline = False - else: code_line += _cend - elif _end: - self.indent -= 1 - self.indent_mod += 1 - else: # \n - self.write_code(code_line.strip(), comment) - self.lineno += 1 - code_line, comment, self.indent_mod = '', '', 0 - if not multiline: - break - - return offset - - def flush_text(self): - text = ''.join(self.text_buffer) - del self.text_buffer[:] - if not text: return - parts, pos, nl = [], 0, '\\\n' + ' ' * self.indent - for m in self.re_inl.finditer(text): - prefix, pos = text[pos:m.start()], m.end() - if prefix: - parts.append(nl.join(map(repr, prefix.splitlines(True)))) - if prefix.endswith('\n'): parts[-1] += nl - parts.append(self.process_inline(m.group(1).strip())) - if pos < len(text): - prefix = text[pos:] - lines = prefix.splitlines(True) - if lines[-1].endswith('\\\\\n'): lines[-1] = lines[-1][:-3] - elif lines[-1].endswith('\\\\\r\n'): lines[-1] = lines[-1][:-4] - parts.append(nl.join(map(repr, lines))) - code = '_printlist((%s,))' % ', '.join(parts) - self.lineno += code.count('\n') + 1 - self.write_code(code) - - @staticmethod - def process_inline(chunk): - if chunk[0] == '!': return '_str(%s)' % chunk[1:] - return '_escape(%s)' % chunk - - def write_code(self, line, comment=''): - code = ' ' * (self.indent + self.indent_mod) - code += line.lstrip() + comment + '\n' - self.code_buffer.append(code) - - -def template(*args, **kwargs): - """ - Get a rendered template as a string iterator. - You can use a name, a filename or a template string as first parameter. - Template rendering arguments can be passed as dictionaries - or directly (as keyword arguments). - """ - tpl = args[0] if args else None - for dictarg in args[1:]: - kwargs.update(dictarg) - adapter = kwargs.pop('template_adapter', SimpleTemplate) - lookup = kwargs.pop('template_lookup', TEMPLATE_PATH) - tplid = (id(lookup), tpl) - if tplid not in TEMPLATES or DEBUG: - settings = kwargs.pop('template_settings', {}) - if isinstance(tpl, adapter): - TEMPLATES[tplid] = tpl - if settings: TEMPLATES[tplid].prepare(**settings) - elif "\n" in tpl or "{" in tpl or "%" in tpl or '$' in tpl: - TEMPLATES[tplid] = adapter(source=tpl, lookup=lookup, **settings) - else: - TEMPLATES[tplid] = adapter(name=tpl, lookup=lookup, **settings) - if not TEMPLATES[tplid]: - abort(500, 'Template (%s) not found' % tpl) - return TEMPLATES[tplid].render(kwargs) - - -mako_template = functools.partial(template, template_adapter=MakoTemplate) -cheetah_template = functools.partial(template, - template_adapter=CheetahTemplate) -jinja2_template = functools.partial(template, template_adapter=Jinja2Template) - - -def view(tpl_name, **defaults): - """ Decorator: renders a template for a handler. - The handler can control its behavior like that: - - - return a dict of template vars to fill out the template - - return something other than a dict and the view decorator will not - process the template, but return the handler result as is. - This includes returning a HTTPResponse(dict) to get, - for instance, JSON with autojson or other castfilters. - """ - - def decorator(func): - - @functools.wraps(func) - def wrapper(*args, **kwargs): - result = func(*args, **kwargs) - if isinstance(result, (dict, DictMixin)): - tplvars = defaults.copy() - tplvars.update(result) - return template(tpl_name, **tplvars) - elif result is None: - return template(tpl_name, **defaults) - return result - - return wrapper - - return decorator - - -mako_view = functools.partial(view, template_adapter=MakoTemplate) -cheetah_view = functools.partial(view, template_adapter=CheetahTemplate) -jinja2_view = functools.partial(view, template_adapter=Jinja2Template) - -############################################################################### -# Constants and Globals ######################################################## -############################################################################### - -TEMPLATE_PATH = ['./', './views/'] -TEMPLATES = {} -DEBUG = False -NORUN = False # If set, run() does nothing. Used by load_app() - -#: A dict to map HTTP status codes (e.g. 404) to phrases (e.g. 'Not Found') -HTTP_CODES = httplib.responses.copy() -HTTP_CODES[418] = "I'm a teapot" # RFC 2324 -HTTP_CODES[428] = "Precondition Required" -HTTP_CODES[429] = "Too Many Requests" -HTTP_CODES[431] = "Request Header Fields Too Large" -HTTP_CODES[451] = "Unavailable For Legal Reasons" # RFC 7725 -HTTP_CODES[511] = "Network Authentication Required" -_HTTP_STATUS_LINES = dict((k, '%d %s' % (k, v)) - for (k, v) in HTTP_CODES.items()) - -#: The default template used for error pages. Override with @error() -ERROR_PAGE_TEMPLATE = """ -%%try: - %%from %s import DEBUG, request - - - - Error: {{e.status}} - - - -

    Error: {{e.status}}

    -

    Sorry, the requested URL {{repr(request.url)}} - caused an error:

    -
    {{e.body}}
    - %%if DEBUG and e.exception: -

    Exception:

    - %%try: - %%exc = repr(e.exception) - %%except: - %%exc = '' %% type(e.exception).__name__ - %%end -
    {{exc}}
    - %%end - %%if DEBUG and e.traceback: -

    Traceback:

    -
    {{e.traceback}}
    - %%end - - -%%except ImportError: - ImportError: Could not generate the error page. Please add bottle to - the import path. -%%end -""" % __name__ - -#: A thread-safe instance of :class:`LocalRequest`. If accessed from within a -#: request callback, this instance always refers to the *current* request -#: (even on a multi-threaded server). -request = LocalRequest() - -#: A thread-safe instance of :class:`LocalResponse`. It is used to change the -#: HTTP response for the *current* request. -response = LocalResponse() - -#: A thread-safe namespace. Not used by Bottle. -local = threading.local() - -# Initialize app stack (create first empty Bottle app now deferred until needed) -# BC: 0.6.4 and needed for run() -apps = app = default_app = AppStack() - -#: A virtual package that redirects import statements. -#: Example: ``import bottle.ext.sqlite`` actually imports `bottle_sqlite`. -ext = _ImportRedirect('bottle.ext' if __name__ == '__main__' else - __name__ + ".ext", 'bottle_%s').module - - -def _main(argv): # pragma: no coverage - args, parser = _cli_parse(argv) - - def _cli_error(cli_msg): - parser.print_help() - _stderr('\nError: %s\n' % cli_msg) - sys.exit(1) - - if args.version: - print('Bottle %s' % __version__) - sys.exit(0) - if not args.app: - _cli_error("No application entry point specified.") - - sys.path.insert(0, '.') - sys.modules.setdefault('bottle', sys.modules['__main__']) - - host, port = (args.bind or 'localhost'), 8080 - if ':' in host and host.rfind(']') < host.rfind(':'): - host, port = host.rsplit(':', 1) - host = host.strip('[]') - - config = ConfigDict() - - for cfile in args.conf or []: - try: - if cfile.endswith('.json'): - with open(cfile, 'rb') as fp: - config.load_dict(json_loads(fp.read())) - else: - config.load_config(cfile) - except configparser.Error as parse_error: - _cli_error(parse_error) - except IOError: - _cli_error("Unable to read config file %r" % cfile) - except (UnicodeError, TypeError, ValueError) as error: - _cli_error("Unable to parse config file %r: %s" % (cfile, error)) - - for cval in args.param or []: - if '=' in cval: - config.update((cval.split('=', 1),)) - else: - config[cval] = True - - run(args.app, - host=host, - port=int(port), - server=args.server, - reloader=args.reload, - plugins=args.plugin, - debug=args.debug, - config=config) - - -def main(): - _main(sys.argv) - - -if __name__ == '__main__': # pragma: no coverage - main() diff --git a/src/myenv/Scripts/convertfromhtml.exe b/src/myenv/Scripts/convertfromhtml.exe deleted file mode 100644 index 32725b6..0000000 Binary files a/src/myenv/Scripts/convertfromhtml.exe and /dev/null differ diff --git a/src/myenv/Scripts/coverage-3.10.exe b/src/myenv/Scripts/coverage-3.10.exe deleted file mode 100644 index b3807dc..0000000 Binary files a/src/myenv/Scripts/coverage-3.10.exe and /dev/null differ diff --git a/src/myenv/Scripts/coverage.exe b/src/myenv/Scripts/coverage.exe deleted file mode 100644 index b3807dc..0000000 Binary files a/src/myenv/Scripts/coverage.exe and /dev/null differ diff --git a/src/myenv/Scripts/coverage3.exe b/src/myenv/Scripts/coverage3.exe deleted file mode 100644 index b3807dc..0000000 Binary files a/src/myenv/Scripts/coverage3.exe and /dev/null differ diff --git a/src/myenv/Scripts/deactivate.bat b/src/myenv/Scripts/deactivate.bat deleted file mode 100644 index 62a39a7..0000000 --- a/src/myenv/Scripts/deactivate.bat +++ /dev/null @@ -1,22 +0,0 @@ -@echo off - -if defined _OLD_VIRTUAL_PROMPT ( - set "PROMPT=%_OLD_VIRTUAL_PROMPT%" -) -set _OLD_VIRTUAL_PROMPT= - -if defined _OLD_VIRTUAL_PYTHONHOME ( - set "PYTHONHOME=%_OLD_VIRTUAL_PYTHONHOME%" - set _OLD_VIRTUAL_PYTHONHOME= -) - -if defined _OLD_VIRTUAL_PATH ( - set "PATH=%_OLD_VIRTUAL_PATH%" -) - -set _OLD_VIRTUAL_PATH= - -set VIRTUAL_ENV= -set VIRTUAL_ENV_PROMPT= - -:END diff --git a/src/myenv/Scripts/django-admin.exe b/src/myenv/Scripts/django-admin.exe deleted file mode 100644 index 3d2d409..0000000 Binary files a/src/myenv/Scripts/django-admin.exe and /dev/null differ diff --git a/src/myenv/Scripts/markdown_py.exe b/src/myenv/Scripts/markdown_py.exe deleted file mode 100644 index 98951d8..0000000 Binary files a/src/myenv/Scripts/markdown_py.exe and /dev/null differ diff --git a/src/myenv/Scripts/normalizer.exe b/src/myenv/Scripts/normalizer.exe deleted file mode 100644 index aed0c2d..0000000 Binary files a/src/myenv/Scripts/normalizer.exe and /dev/null differ diff --git a/src/myenv/Scripts/openai.exe b/src/myenv/Scripts/openai.exe deleted file mode 100644 index 2d94b3f..0000000 Binary files a/src/myenv/Scripts/openai.exe and /dev/null differ diff --git a/src/myenv/Scripts/pip.exe b/src/myenv/Scripts/pip.exe deleted file mode 100644 index 0c0e2b8..0000000 Binary files a/src/myenv/Scripts/pip.exe and /dev/null differ diff --git a/src/myenv/Scripts/pip3.10.exe b/src/myenv/Scripts/pip3.10.exe deleted file mode 100644 index 0c0e2b8..0000000 Binary files a/src/myenv/Scripts/pip3.10.exe and /dev/null differ diff --git a/src/myenv/Scripts/pip3.exe b/src/myenv/Scripts/pip3.exe deleted file mode 100644 index 0c0e2b8..0000000 Binary files a/src/myenv/Scripts/pip3.exe and /dev/null differ diff --git a/src/myenv/Scripts/pygmentize.exe b/src/myenv/Scripts/pygmentize.exe deleted file mode 100644 index c72b375..0000000 Binary files a/src/myenv/Scripts/pygmentize.exe and /dev/null differ diff --git a/src/myenv/Scripts/python.exe b/src/myenv/Scripts/python.exe deleted file mode 100644 index 8655d9d..0000000 Binary files a/src/myenv/Scripts/python.exe and /dev/null differ diff --git a/src/myenv/Scripts/pythonw.exe b/src/myenv/Scripts/pythonw.exe deleted file mode 100644 index b19f1fd..0000000 Binary files a/src/myenv/Scripts/pythonw.exe and /dev/null differ diff --git a/src/myenv/Scripts/slugify.exe b/src/myenv/Scripts/slugify.exe deleted file mode 100644 index 702496d..0000000 Binary files a/src/myenv/Scripts/slugify.exe and /dev/null differ diff --git a/src/myenv/Scripts/sqlformat.exe b/src/myenv/Scripts/sqlformat.exe deleted file mode 100644 index 39367df..0000000 Binary files a/src/myenv/Scripts/sqlformat.exe and /dev/null differ diff --git a/src/myenv/Scripts/tqdm.exe b/src/myenv/Scripts/tqdm.exe deleted file mode 100644 index 3f25dc0..0000000 Binary files a/src/myenv/Scripts/tqdm.exe and /dev/null differ diff --git a/src/myenv/pyvenv.cfg b/src/myenv/pyvenv.cfg deleted file mode 100644 index 55ce0a4..0000000 --- a/src/myenv/pyvenv.cfg +++ /dev/null @@ -1,3 +0,0 @@ -home = G:\py310 -include-system-site-packages = false -version = 3.10.11 diff --git a/src/oauth/admin.py b/src/oauth/admin.py deleted file mode 100644 index 1f6b21b..0000000 --- a/src/oauth/admin.py +++ /dev/null @@ -1,68 +0,0 @@ -import logging # 导入日志模块,用于记录日志信息 - -from django.contrib import admin # 导入Django的admin模块,用于管理后台 -# Register your models here. -from django.urls import reverse # 导入reverse函数,用于生成URL -from django.utils.html import format_html # 导入format_html函数,用于生成HTML内容 - -logger = logging.getLogger(__name__) # 获取当前模块的日志记录器 - -# 定义OAuthUserAdmin类,用于自定义OAuth用户模型在Django Admin中的显示和管理 -class OAuthUserAdmin(admin.ModelAdmin): - # 定义搜索字段,支持通过昵称和邮箱搜索 - search_fields = ('nickname', 'email') - # 每页显示的记录数 - list_per_page = 20 - # 定义列表页显示的字段 - list_display = ( - 'id', # 用户ID - 'nickname', # 用户昵称 - 'link_to_usermodel', # 链接到用户模型 - 'show_user_image', # 显示用户头像 - 'type', # 用户类型 - 'email', # 用户邮箱 - ) - # 定义可以点击进入详情页的字段 - list_display_links = ('id', 'nickname') - # 定义过滤器,支持按作者和类型过滤 - list_filter = ('author', 'type',) - # 定义只读字段,初始为空 - readonly_fields = [] - - # 动态获取只读字段 - def get_readonly_fields(self, request, obj=None): - return list(self.readonly_fields) + \ - [field.name for field in obj._meta.fields] + [field.name for field in obj._meta.many_to_many] # 获取所有多对多字段 - - - # 禁止添加新记录 - def has_add_permission(self, request): - return False - - # 定义一个方法,生成指向用户模型的链接 - def link_to_usermodel(self, obj): - if obj.author: # 如果存在作者 - info = (obj.author._meta.app_label, obj.author._meta.model_name) # 获取作者模型的app_label和model_name - link = reverse('admin:%s_%s_change' % info, args=(obj.author.id,)) # 生成指向作者详情页的链接 - return format_html( - u'%s' % # 生成HTML超链接 - (link, obj.author.nickname if obj.author.nickname else obj.author.email)) # 显示昵称或邮箱 - - # 定义一个方法,显示用户头像 - def show_user_image(self, obj): - img = obj.picture # 获取用户头像URL - return format_html( - u'' % # 生成HTML图片标签 - (img)) - - # 设置方法的显示名称 - link_to_usermodel.short_description = '用户' - show_user_image.short_description = '用户头像' - - -# 定义OAuthConfigAdmin类,用于自定义OAuth配置模型在Django Admin中的显示和管理 -class OAuthConfigAdmin(admin.ModelAdmin): - # 定义列表页显示的字段 - list_display = ('type', 'appkey', 'appsecret', 'is_enable') - # 定义过滤器,支持按类型过滤 - list_filter = ('type',) \ No newline at end of file diff --git a/src/plugins/article_recommendation/__init__.py b/src/plugins/article_recommendation/__init__.py deleted file mode 100644 index 951f2ff..0000000 --- a/src/plugins/article_recommendation/__init__.py +++ /dev/null @@ -1 +0,0 @@ -# 文章推荐插件 diff --git a/src/plugins/article_recommendation/plugin.py b/src/plugins/article_recommendation/plugin.py deleted file mode 100644 index 6656a07..0000000 --- a/src/plugins/article_recommendation/plugin.py +++ /dev/null @@ -1,205 +0,0 @@ -import logging -from djangoblog.plugin_manage.base_plugin import BasePlugin -from djangoblog.plugin_manage import hooks -from djangoblog.plugin_manage.hook_constants import ARTICLE_DETAIL_LOAD -from blog.models import Article - -logger = logging.getLogger(__name__) - - -class ArticleRecommendationPlugin(BasePlugin): - PLUGIN_NAME = '文章推荐' - PLUGIN_DESCRIPTION = '智能文章推荐系统,支持多位置展示' - PLUGIN_VERSION = '1.0.0' - PLUGIN_AUTHOR = 'liangliangyy' - - # 支持的位置 - SUPPORTED_POSITIONS = ['article_bottom'] - - # 各位置优先级 - POSITION_PRIORITIES = { - 'article_bottom': 80, # 文章底部优先级 - } - - # 插件配置 - CONFIG = { - 'article_bottom_count': 8, # 文章底部推荐数量 - 'sidebar_count': 5, # 侧边栏推荐数量 - 'enable_category_fallback': True, # 启用分类回退 - 'enable_popular_fallback': True, # 启用热门文章回退 - } - - def register_hooks(self): - """注册钩子""" - hooks.register(ARTICLE_DETAIL_LOAD, self.on_article_detail_load) - - def on_article_detail_load(self, article, context, request, *args, **kwargs): - """文章详情页加载时的处理""" - # 可以在这里预加载推荐数据到context中 - recommendations = self.get_recommendations(article) - context['article_recommendations'] = recommendations - - def should_display(self, position, context, **kwargs): - """条件显示逻辑""" - # 只在文章详情页底部显示 - if position == 'article_bottom': - article = kwargs.get('article') or context.get('article') - # 检查是否有文章对象,以及是否不是索引页面 - is_index = context.get('isindex', False) if hasattr(context, 'get') else False - return article is not None and not is_index - - return False - - def render_article_bottom_widget(self, context, **kwargs): - """渲染文章底部推荐""" - article = kwargs.get('article') or context.get('article') - if not article: - return None - - # 使用配置的数量,也可以通过kwargs覆盖 - count = kwargs.get('count', self.CONFIG['article_bottom_count']) - recommendations = self.get_recommendations(article, count=count) - if not recommendations: - return None - - # 将RequestContext转换为普通字典 - context_dict = {} - if hasattr(context, 'flatten'): - context_dict = context.flatten() - elif hasattr(context, 'dicts'): - # 合并所有上下文字典 - for d in context.dicts: - context_dict.update(d) - - template_context = { - 'recommendations': recommendations, - 'article': article, - 'title': '相关推荐', - **context_dict - } - - return self.render_template('bottom_widget.html', template_context) - - def render_sidebar_widget(self, context, **kwargs): - """渲染侧边栏推荐""" - article = context.get('article') - - # 使用配置的数量,也可以通过kwargs覆盖 - count = kwargs.get('count', self.CONFIG['sidebar_count']) - - if article: - # 文章页面,显示相关文章 - recommendations = self.get_recommendations(article, count=count) - title = '相关文章' - else: - # 其他页面,显示热门文章 - recommendations = self.get_popular_articles(count=count) - title = '热门推荐' - - if not recommendations: - return None - - # 将RequestContext转换为普通字典 - context_dict = {} - if hasattr(context, 'flatten'): - context_dict = context.flatten() - elif hasattr(context, 'dicts'): - # 合并所有上下文字典 - for d in context.dicts: - context_dict.update(d) - - template_context = { - 'recommendations': recommendations, - 'title': title, - **context_dict - } - - return self.render_template('sidebar_widget.html', template_context) - - def get_css_files(self): - """返回CSS文件""" - return ['css/recommendation.css'] - - def get_js_files(self): - """返回JS文件""" - return ['js/recommendation.js'] - - def get_recommendations(self, article, count=5): - """获取推荐文章""" - if not article: - return [] - - recommendations = [] - - # 1. 基于标签的推荐 - if article.tags.exists(): - tag_ids = list(article.tags.values_list('id', flat=True)) - tag_based = list(Article.objects.filter( - status='p', - tags__id__in=tag_ids - ).exclude( - id=article.id - ).exclude( - title__isnull=True - ).exclude( - title__exact='' - ).distinct().order_by('-views')[:count]) - recommendations.extend(tag_based) - - # 2. 如果数量不够,基于分类推荐 - if len(recommendations) < count and self.CONFIG['enable_category_fallback']: - needed = count - len(recommendations) - existing_ids = [r.id for r in recommendations] + [article.id] - - category_based = list(Article.objects.filter( - status='p', - category=article.category - ).exclude( - id__in=existing_ids - ).exclude( - title__isnull=True - ).exclude( - title__exact='' - ).order_by('-views')[:needed]) - recommendations.extend(category_based) - - # 3. 如果还是不够,推荐热门文章 - if len(recommendations) < count and self.CONFIG['enable_popular_fallback']: - needed = count - len(recommendations) - existing_ids = [r.id for r in recommendations] + [article.id] - - popular_articles = list(Article.objects.filter( - status='p' - ).exclude( - id__in=existing_ids - ).exclude( - title__isnull=True - ).exclude( - title__exact='' - ).order_by('-views')[:needed]) - recommendations.extend(popular_articles) - - # 过滤掉无效的推荐 - valid_recommendations = [] - for rec in recommendations: - if rec.title and len(rec.title.strip()) > 0: - valid_recommendations.append(rec) - else: - logger.warning(f"过滤掉空标题文章: ID={rec.id}, 标题='{rec.title}'") - - # 调试:记录推荐结果 - logger.info(f"原始推荐数量: {len(recommendations)}, 有效推荐数量: {len(valid_recommendations)}") - for i, rec in enumerate(valid_recommendations): - logger.info(f"推荐 {i+1}: ID={rec.id}, 标题='{rec.title}', 长度={len(rec.title)}") - - return valid_recommendations[:count] - - def get_popular_articles(self, count=3): - """获取热门文章""" - return list(Article.objects.filter( - status='p' - ).order_by('-views')[:count]) - - -# 实例化插件 -plugin = ArticleRecommendationPlugin() diff --git a/src/plugins/article_recommendation/static/article_recommendation/css/recommendation.css b/src/plugins/article_recommendation/static/article_recommendation/css/recommendation.css deleted file mode 100644 index b223f41..0000000 --- a/src/plugins/article_recommendation/static/article_recommendation/css/recommendation.css +++ /dev/null @@ -1,166 +0,0 @@ -/* 文章推荐插件样式 - 与网站风格保持一致 */ - -/* 文章底部推荐样式 */ -.article-recommendations { - margin: 30px 0; - padding: 20px; - background: #fff; - border: 1px solid #e1e1e1; - border-radius: 3px; - box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1); -} - -.recommendations-title { - margin: 0 0 15px 0; - font-size: 18px; - color: #444; - font-weight: bold; - padding-bottom: 8px; - border-bottom: 2px solid #21759b; - display: inline-block; -} - -.recommendations-icon { - margin-right: 5px; - font-size: 16px; -} - -.recommendations-grid { - display: grid; - gap: 15px; - grid-template-columns: 1fr; - margin-top: 15px; -} - -.recommendation-card { - background: #fff; - border: 1px solid #e1e1e1; - border-radius: 3px; - transition: all 0.2s ease; - overflow: hidden; -} - -.recommendation-card:hover { - border-color: #21759b; - box-shadow: 0 2px 5px rgba(33, 117, 155, 0.1); -} - -.recommendation-link { - display: block; - padding: 15px; - text-decoration: none; - color: inherit; -} - -.recommendation-title { - margin: 0 0 8px 0; - font-size: 15px; - font-weight: normal; - color: #444; - line-height: 1.4; - transition: color 0.2s ease; -} - -.recommendation-card:hover .recommendation-title { - color: #21759b; -} - -.recommendation-meta { - display: flex; - justify-content: space-between; - align-items: center; - font-size: 12px; - color: #757575; -} - -.recommendation-category { - background: #ebebeb; - color: #5e5e5e; - padding: 2px 6px; - border-radius: 2px; - font-size: 11px; - font-weight: normal; -} - -.recommendation-date { - font-weight: normal; - color: #757575; -} - -/* 侧边栏推荐样式 */ -.widget_recommendations { - margin-bottom: 20px; -} - -.widget_recommendations .widget-title { - font-size: 16px; - font-weight: bold; - margin-bottom: 15px; - color: #333; - border-bottom: 2px solid #007cba; - padding-bottom: 5px; -} - -.recommendations-list { - list-style: none; - padding: 0; - margin: 0; -} - -.recommendations-list .recommendation-item { - padding: 8px 0; - border-bottom: 1px solid #eee; - background: none; - border: none; - border-radius: 0; -} - -.recommendations-list .recommendation-item:last-child { - border-bottom: none; -} - -.recommendations-list .recommendation-item a { - color: #333; - text-decoration: none; - font-size: 14px; - line-height: 1.4; - display: block; - margin-bottom: 4px; - transition: color 0.3s ease; -} - -.recommendations-list .recommendation-item a:hover { - color: #007cba; -} - -.recommendations-list .recommendation-meta { - font-size: 11px; - color: #999; - margin: 0; -} - -.recommendations-list .recommendation-meta span { - margin-right: 10px; -} - -/* 响应式设计 - 分栏显示 */ -@media (min-width: 768px) { - .recommendations-grid { - grid-template-columns: repeat(2, 1fr); - gap: 15px; - } -} - -@media (min-width: 1024px) { - .recommendations-grid { - grid-template-columns: repeat(3, 1fr); - gap: 15px; - } -} - -@media (min-width: 1200px) { - .recommendations-grid { - grid-template-columns: repeat(4, 1fr); - gap: 15px; - } -} diff --git a/src/plugins/article_recommendation/static/article_recommendation/js/recommendation.js b/src/plugins/article_recommendation/static/article_recommendation/js/recommendation.js deleted file mode 100644 index eb19211..0000000 --- a/src/plugins/article_recommendation/static/article_recommendation/js/recommendation.js +++ /dev/null @@ -1,93 +0,0 @@ -/** - * 文章推荐插件JavaScript - */ - -(function() { - 'use strict'; - - // 等待DOM加载完成 - document.addEventListener('DOMContentLoaded', function() { - initRecommendations(); - }); - - function initRecommendations() { - // 添加点击统计 - trackRecommendationClicks(); - - // 懒加载优化(如果需要) - lazyLoadRecommendations(); - } - - function trackRecommendationClicks() { - const recommendationLinks = document.querySelectorAll('.recommendation-item a'); - - recommendationLinks.forEach(function(link) { - link.addEventListener('click', function(e) { - // 可以在这里添加点击统计逻辑 - const articleTitle = this.textContent.trim(); - const articleUrl = this.href; - - // 发送统计数据到后端(可选) - if (typeof gtag !== 'undefined') { - gtag('event', 'click', { - 'event_category': 'recommendation', - 'event_label': articleTitle, - 'value': 1 - }); - } - - console.log('Recommendation clicked:', articleTitle, articleUrl); - }); - }); - } - - function lazyLoadRecommendations() { - // 如果推荐内容很多,可以实现懒加载 - const recommendationContainer = document.querySelector('.article-recommendations'); - - if (!recommendationContainer) { - return; - } - - // 检查是否在视窗中 - const observer = new IntersectionObserver(function(entries) { - entries.forEach(function(entry) { - if (entry.isIntersecting) { - entry.target.classList.add('loaded'); - observer.unobserve(entry.target); - } - }); - }, { - threshold: 0.1 - }); - - const recommendationItems = document.querySelectorAll('.recommendation-item'); - recommendationItems.forEach(function(item) { - observer.observe(item); - }); - } - - // 添加一些动画效果 - function addAnimations() { - const recommendationItems = document.querySelectorAll('.recommendation-item'); - - recommendationItems.forEach(function(item, index) { - item.style.opacity = '0'; - item.style.transform = 'translateY(20px)'; - item.style.transition = 'opacity 0.5s ease, transform 0.5s ease'; - - setTimeout(function() { - item.style.opacity = '1'; - item.style.transform = 'translateY(0)'; - }, index * 100); - }); - } - - // 如果需要,可以在这里添加更多功能 - window.ArticleRecommendation = { - init: initRecommendations, - track: trackRecommendationClicks, - animate: addAnimations - }; - -})(); diff --git a/src/plugins/image_lazy_loading/__init__.py b/src/plugins/image_lazy_loading/__init__.py deleted file mode 100644 index 2d27de0..0000000 --- a/src/plugins/image_lazy_loading/__init__.py +++ /dev/null @@ -1 +0,0 @@ -# Image Lazy Loading Plugin diff --git a/src/plugins/image_lazy_loading/plugin.py b/src/plugins/image_lazy_loading/plugin.py deleted file mode 100644 index b4b9e0a..0000000 --- a/src/plugins/image_lazy_loading/plugin.py +++ /dev/null @@ -1,182 +0,0 @@ -import re -import hashlib -from urllib.parse import urlparse -from djangoblog.plugin_manage.base_plugin import BasePlugin -from djangoblog.plugin_manage import hooks -from djangoblog.plugin_manage.hook_constants import ARTICLE_CONTENT_HOOK_NAME - - -class ImageOptimizationPlugin(BasePlugin): - PLUGIN_NAME = '图片性能优化插件' - PLUGIN_DESCRIPTION = '自动为文章中的图片添加懒加载、异步解码等性能优化属性,显著提升页面加载速度。' - PLUGIN_VERSION = '1.0.0' - PLUGIN_AUTHOR = 'liangliangyy' - - def __init__(self): - # 插件配置 - self.config = { - 'enable_lazy_loading': True, # 启用懒加载 - 'enable_async_decoding': True, # 启用异步解码 - 'add_loading_placeholder': True, # 添加加载占位符 - 'optimize_external_images': True, # 优化外部图片 - 'add_responsive_attributes': True, # 添加响应式属性 - 'skip_first_image': True, # 跳过第一张图片(LCP优化) - } - super().__init__() - - def register_hooks(self): - hooks.register(ARTICLE_CONTENT_HOOK_NAME, self.optimize_images) - - def optimize_images(self, content, *args, **kwargs): - """ - 优化文章中的图片标签 - """ - if not content: - return content - - # 正则表达式匹配 img 标签 - img_pattern = re.compile( - r']*?)(?:\s*/)?>', - re.IGNORECASE | re.DOTALL - ) - - image_count = 0 - - def replace_img_tag(match): - nonlocal image_count - image_count += 1 - - # 获取原始属性 - original_attrs = match.group(1) - - # 解析现有属性 - attrs = self._parse_img_attributes(original_attrs) - - # 应用优化 - optimized_attrs = self._apply_optimizations(attrs, image_count) - - # 重构 img 标签 - return self._build_img_tag(optimized_attrs) - - # 替换所有 img 标签 - optimized_content = img_pattern.sub(replace_img_tag, content) - - return optimized_content - - def _parse_img_attributes(self, attr_string): - """ - 解析 img 标签的属性 - """ - attrs = {} - - # 正则表达式匹配属性 - attr_pattern = re.compile(r'(\w+)=(["\'])(.*?)\2') - - for match in attr_pattern.finditer(attr_string): - attr_name = match.group(1).lower() - attr_value = match.group(3) - attrs[attr_name] = attr_value - - return attrs - - def _apply_optimizations(self, attrs, image_index): - """ - 应用各种图片优化 - """ - # 1. 懒加载优化(跳过第一张图片以优化LCP) - if self.config['enable_lazy_loading']: - if not (self.config['skip_first_image'] and image_index == 1): - if 'loading' not in attrs: - attrs['loading'] = 'lazy' - - # 2. 异步解码 - if self.config['enable_async_decoding']: - if 'decoding' not in attrs: - attrs['decoding'] = 'async' - - # 3. 添加样式优化 - current_style = attrs.get('style', '') - - # 确保图片不会超出容器 - if 'max-width' not in current_style: - if current_style and not current_style.endswith(';'): - current_style += ';' - current_style += 'max-width:100%;height:auto;' - attrs['style'] = current_style - - # 4. 添加 alt 属性(SEO和可访问性) - if 'alt' not in attrs: - # 尝试从图片URL生成有意义的alt文本 - src = attrs.get('src', '') - if src: - # 从文件名生成alt文本 - filename = src.split('/')[-1].split('.')[0] - # 移除常见的无意义字符 - clean_name = re.sub(r'[0-9a-f]{8,}', '', filename) # 移除长hash - clean_name = re.sub(r'[_-]+', ' ', clean_name).strip() - attrs['alt'] = clean_name if clean_name else '文章图片' - else: - attrs['alt'] = '文章图片' - - # 5. 外部图片优化 - if self.config['optimize_external_images'] and 'src' in attrs: - src = attrs['src'] - parsed_url = urlparse(src) - - # 如果是外部图片,添加 referrerpolicy - if parsed_url.netloc and parsed_url.netloc != self._get_current_domain(): - attrs['referrerpolicy'] = 'no-referrer-when-downgrade' - # 为外部图片添加crossorigin属性以支持性能监控 - if 'crossorigin' not in attrs: - attrs['crossorigin'] = 'anonymous' - - # 6. 响应式图片属性(如果配置启用) - if self.config['add_responsive_attributes']: - # 添加 sizes 属性(如果没有的话) - if 'sizes' not in attrs and 'srcset' not in attrs: - attrs['sizes'] = '(max-width: 768px) 100vw, (max-width: 1024px) 50vw, 33vw' - - # 7. 添加图片唯一标识符用于性能追踪 - if 'data-img-id' not in attrs and 'src' in attrs: - img_hash = hashlib.md5(attrs['src'].encode()).hexdigest()[:8] - attrs['data-img-id'] = f'img-{img_hash}' - - # 8. 为第一张图片添加高优先级提示(LCP优化) - if image_index == 1 and self.config['skip_first_image']: - attrs['fetchpriority'] = 'high' - # 移除懒加载以确保快速加载 - if 'loading' in attrs: - del attrs['loading'] - - return attrs - - def _build_img_tag(self, attrs): - """ - 重新构建 img 标签 - """ - attr_strings = [] - - # 确保 src 属性在最前面 - if 'src' in attrs: - attr_strings.append(f'src="{attrs["src"]}"') - - # 添加其他属性 - for key, value in attrs.items(): - if key != 'src': # src 已经添加过了 - attr_strings.append(f'{key}="{value}"') - - return f'' - - def _get_current_domain(self): - """ - 获取当前网站域名 - """ - try: - from djangoblog.utils import get_current_site - return get_current_site().domain - except: - return '' - - -# 实例化插件 -plugin = ImageOptimizationPlugin() diff --git a/src/servermanager/MemcacheStorage.py b/src/servermanager/MemcacheStorage.py deleted file mode 100644 index f7aea38..0000000 --- a/src/servermanager/MemcacheStorage.py +++ /dev/null @@ -1,45 +0,0 @@ -from werobot.session import SessionStorage -from werobot.utils import json_loads, json_dumps - -from djangoblog.utils import cache -# 导入所需的模块和工具,包括缓存工具和JSON序列化/反序列化工具 - -# 定义MemcacheStorage类,继承自SessionStorage,用于实现基于缓存的会话存储 -class MemcacheStorage(SessionStorage): - def __init__(self, prefix='ws_'): - # 初始化MemcacheStorage实例 - # 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): - # 根据给定的键生成带前缀的完整键名 - # s: 原始键 - return '{prefix}{s}'.format(prefix=self.prefix, s=s) - - def get(self, id): - # 从缓存中获取会话数据 - # id: 会话的唯一标识 - id = self.key_name(id) # 生成完整键名 - session_json = self.cache.get(id) or '{}' # 从缓存中获取数据,若不存在则返回空JSON - return json_loads(session_json) # 将JSON字符串反序列化为Python对象 - - def set(self, id, value): - # 将会话数据存储到缓存中 - # id: 会话的唯一标识 - # value: 要存储的会话数据 - id = self.key_name(id) # 生成完整键名 - self.cache.set(id, json_dumps(value)) # 将数据序列化为JSON字符串后存储到缓存 - - def delete(self, id): - # 从缓存中删除会话数据 - # id: 会话的唯一标识 - id = self.key_name(id) # 生成完整键名 - self.cache.delete(id) # 删除缓存中的数据 \ No newline at end of file diff --git a/src/servermanager/admin.py b/src/servermanager/admin.py deleted file mode 100644 index 827a9d8..0000000 --- a/src/servermanager/admin.py +++ /dev/null @@ -1,24 +0,0 @@ -from django.contrib import admin -# 导入Django的admin模块,用于注册和管理模型 - -# 定义CommandsAdmin类,用于自定义Commands模型在Django Admin中的显示和行为 -class CommandsAdmin(admin.ModelAdmin): - # 指定在列表页面中显示的字段 - list_display = ('title', 'command', 'describe') - - -# 定义EmailSendLogAdmin类,用于自定义EmailSendLog模型在Django Admin中的显示和行为 -class EmailSendLogAdmin(admin.ModelAdmin): - # 指定在列表页面中显示的字段 - list_display = ('title', 'emailto', 'send_result', 'creation_time') - # 指定只读字段,防止这些字段在Admin中被修改 - readonly_fields = ( - 'title', - 'emailto', - 'send_result', - 'creation_time', - 'content') - - # 禁止在Admin中添加新的EmailSendLog记录 - def has_add_permission(self, request): - return False diff --git a/src/servermanager/api/blogapi.py b/src/servermanager/api/blogapi.py deleted file mode 100644 index 8a4d6ac..0000000 --- a/src/servermanager/api/blogapi.py +++ /dev/null @@ -1,27 +0,0 @@ -from haystack.query import SearchQuerySet - -from blog.models import Article, Category - - -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__] diff --git a/src/templates/plugins/article_recommendation/__init__.py b/src/templates/plugins/article_recommendation/__init__.py deleted file mode 100644 index 7d86a99..0000000 --- a/src/templates/plugins/article_recommendation/__init__.py +++ /dev/null @@ -1 +0,0 @@ -# 插件模板目录 diff --git a/src/templates/plugins/article_recommendation/bottom_widget.html b/src/templates/plugins/article_recommendation/bottom_widget.html deleted file mode 100644 index 829b7b4..0000000 --- a/src/templates/plugins/article_recommendation/bottom_widget.html +++ /dev/null @@ -1,23 +0,0 @@ -{% load i18n %} -
    -

    - 📖{{ title }} -

    -
    - {% for article in recommendations %} - {% if article.title and article.title|length > 0 %} - - {% endif %} - {% endfor %} -
    -
    diff --git a/src/templates/plugins/article_recommendation/sidebar_widget.html b/src/templates/plugins/article_recommendation/sidebar_widget.html deleted file mode 100644 index 5f1afbf..0000000 --- a/src/templates/plugins/article_recommendation/sidebar_widget.html +++ /dev/null @@ -1,17 +0,0 @@ -{% load i18n %} - diff --git a/src/templates/plugins/css_includes.html b/src/templates/plugins/css_includes.html deleted file mode 100644 index 37029ae..0000000 --- a/src/templates/plugins/css_includes.html +++ /dev/null @@ -1,4 +0,0 @@ -{% comment %}插件CSS文件包含模板 - 用于压缩{% endcomment %} -{% for css_file in css_files %} - -{% endfor %} diff --git a/src/templates/plugins/js_includes.html b/src/templates/plugins/js_includes.html deleted file mode 100644 index 2a315e3..0000000 --- a/src/templates/plugins/js_includes.html +++ /dev/null @@ -1,4 +0,0 @@ -{% comment %}插件JS文件包含模板 - 用于压缩{% endcomment %} -{% for js_file in js_files %} - -{% endfor %}