diff --git a/src/DjangoBlog/.dockerignore b/src/DjangoBlog/.dockerignore deleted file mode 100644 index becd6f9..0000000 --- a/src/DjangoBlog/.dockerignore +++ /dev/null @@ -1,12 +0,0 @@ -bin/data/ -# virtualenv -venv/ -collectedstatic/ -djangoblog/whoosh_index/ -uploads/ -settings_production.py -*.md -docs/ -logs/ -static/ -.github/ diff --git a/src/DjangoBlog/.gitattributes b/src/DjangoBlog/.gitattributes deleted file mode 100644 index fd52ece..0000000 --- a/src/DjangoBlog/.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/DjangoBlog/.github/ISSUE_TEMPLATE.md b/src/DjangoBlog/.github/ISSUE_TEMPLATE.md deleted file mode 100644 index 2b5b7aa..0000000 --- a/src/DjangoBlog/.github/ISSUE_TEMPLATE.md +++ /dev/null @@ -1,18 +0,0 @@ - - -**我确定我已经查看了** (标注`[ ]`为`[x]`) - -- [ ] [DjangoBlog的readme](https://github.com/liangliangyy/DjangoBlog/blob/master/README.md) -- [ ] [配置说明](https://github.com/liangliangyy/DjangoBlog/blob/master/bin/config.md) -- [ ] [其他 Issues](https://github.com/liangliangyy/DjangoBlog/issues) - ----- - -**我要申请** (标注`[ ]`为`[x]`) - -- [ ] BUG 反馈 -- [ ] 添加新的特性或者功能 -- [ ] 请求技术支持 diff --git a/src/DjangoBlog/.github/workflows/codeql-analysis.yml b/src/DjangoBlog/.github/workflows/codeql-analysis.yml deleted file mode 100644 index 52775e0..0000000 --- a/src/DjangoBlog/.github/workflows/codeql-analysis.yml +++ /dev/null @@ -1,49 +0,0 @@ -name: "CodeQL" - -on: - push: - branches: - - master - - dev - paths-ignore: - - '**/*.md' - - '**/*.css' - - '**/*.js' - - '**/*.yml' - - '**/*.txt' - pull_request: - branches: - - master - - dev - paths-ignore: - - '**/*.md' - - '**/*.css' - - '**/*.js' - - '**/*.yml' - - '**/*.txt' - schedule: - - cron: '30 1 * * 0' - - -jobs: - CodeQL-Build: - runs-on: ubuntu-latest - permissions: - security-events: write - actions: read - contents: read - - steps: - - name: Checkout repository - uses: actions/checkout@v3 - - - name: Initialize CodeQL - uses: github/codeql-action/init@v3 - with: - languages: python - - - name: Autobuild - uses: github/codeql-action/autobuild@v3 - - - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@v3 \ No newline at end of file diff --git a/src/DjangoBlog/.github/workflows/deploy-master.yml b/src/DjangoBlog/.github/workflows/deploy-master.yml deleted file mode 100644 index c07a326..0000000 --- a/src/DjangoBlog/.github/workflows/deploy-master.yml +++ /dev/null @@ -1,176 +0,0 @@ -name: 自动部署到生产环境 - -on: - workflow_run: - workflows: ["Django CI"] - types: - - completed - branches: - - master - workflow_dispatch: - inputs: - environment: - description: '部署环境' - required: true - default: 'production' - type: choice - options: - - production - - staging - image_tag: - description: '镜像标签 (默认: latest)' - required: false - default: 'latest' - type: string - skip_tests: - description: '跳过测试直接部署' - required: false - default: false - type: boolean - -env: - REGISTRY: registry.cn-shenzhen.aliyuncs.com - IMAGE_NAME: liangliangyy/djangoblog - NAMESPACE: djangoblog - -jobs: - deploy: - name: 构建镜像并部署到生产环境 - runs-on: ubuntu-latest - if: ${{ github.event.workflow_run.conclusion == 'success' || github.event_name == 'workflow_dispatch' }} - - steps: - - name: 检出代码 - uses: actions/checkout@v4 - - - name: 设置部署参数 - id: deploy-params - run: | - if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then - echo "trigger_type=手动触发" >> $GITHUB_OUTPUT - echo "environment=${{ github.event.inputs.environment }}" >> $GITHUB_OUTPUT - echo "image_tag=${{ github.event.inputs.image_tag }}" >> $GITHUB_OUTPUT - echo "skip_tests=${{ github.event.inputs.skip_tests }}" >> $GITHUB_OUTPUT - else - echo "trigger_type=CI自动触发" >> $GITHUB_OUTPUT - echo "environment=production" >> $GITHUB_OUTPUT - echo "image_tag=latest" >> $GITHUB_OUTPUT - echo "skip_tests=false" >> $GITHUB_OUTPUT - fi - - - name: 显示部署信息 - run: | - echo "🚀 部署信息:" - echo " 触发方式: ${{ steps.deploy-params.outputs.trigger_type }}" - echo " 部署环境: ${{ steps.deploy-params.outputs.environment }}" - echo " 镜像标签: ${{ steps.deploy-params.outputs.image_tag }}" - echo " 跳过测试: ${{ steps.deploy-params.outputs.skip_tests }}" - - - name: 设置Docker Buildx - uses: docker/setup-buildx-action@v3 - - - name: 登录私有镜像仓库 - uses: docker/login-action@v3 - with: - registry: ${{ env.REGISTRY }} - username: ${{ secrets.REGISTRY_USERNAME }} - password: ${{ secrets.REGISTRY_PASSWORD }} - - - name: 提取镜像元数据 - id: meta - uses: docker/metadata-action@v5 - with: - images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} - tags: | - type=ref,event=branch - type=sha,prefix={{branch}}- - type=raw,value=${{ steps.deploy-params.outputs.image_tag }} - - - name: 构建并推送Docker镜像 - uses: docker/build-push-action@v5 - with: - context: . - file: ./Dockerfile - push: true - tags: ${{ steps.meta.outputs.tags }} - labels: ${{ steps.meta.outputs.labels }} - cache-from: type=gha - cache-to: type=gha,mode=max - platforms: linux/amd64 - - - name: 部署到生产服务器 - uses: appleboy/ssh-action@v1.0.3 - with: - host: ${{ secrets.PRODUCTION_HOST }} - username: ${{ secrets.PRODUCTION_USER }} - key: ${{ secrets.PRODUCTION_SSH_KEY }} - port: ${{ secrets.PRODUCTION_PORT || 22 }} - script: | - echo "🚀 开始部署 DjangoBlog..." - - # 检查kubectl是否可用 - if ! command -v kubectl &> /dev/null; then - echo "❌ 错误: kubectl 未安装或不在PATH中" - exit 1 - fi - - # 检查命名空间是否存在 - if ! kubectl get namespace ${{ env.NAMESPACE }} &> /dev/null; then - echo "❌ 错误: 命名空间 ${{ env.NAMESPACE }} 不存在" - exit 1 - fi - - # 更新deployment镜像 - echo "📦 更新deployment镜像为: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.deploy-params.outputs.image_tag }}" - kubectl set image deployment/djangoblog \ - djangoblog=${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.deploy-params.outputs.image_tag }} \ - -n ${{ env.NAMESPACE }} - - # 重启deployment - echo "🔄 重启deployment..." - kubectl -n ${{ env.NAMESPACE }} rollout restart deployment djangoblog - - # 等待deployment完成 - echo "⏳ 等待deployment完成..." - kubectl rollout status deployment/djangoblog -n ${{ env.NAMESPACE }} --timeout=300s - - # 检查deployment状态 - echo "✅ 检查deployment状态..." - kubectl get deployment djangoblog -n ${{ env.NAMESPACE }} - kubectl get pods -l app=djangoblog -n ${{ env.NAMESPACE }} - - echo "🎉 部署完成!" - - - name: 发送部署通知 - if: always() - run: | - # 设置通知内容 - if [ "${{ job.status }}" = "success" ]; then - TITLE="✅ DjangoBlog部署成功" - STATUS="成功" - else - TITLE="❌ DjangoBlog部署失败" - STATUS="失败" - fi - - MESSAGE="部署状态: ${STATUS} - 触发方式: ${{ steps.deploy-params.outputs.trigger_type }} - 部署环境: ${{ steps.deploy-params.outputs.environment }} - 镜像标签: ${{ steps.deploy-params.outputs.image_tag }} - 提交者: ${{ github.actor }} - 时间: $(date '+%Y-%m-%d %H:%M:%S') - - 查看详情: https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}" - - # 发送Server酱通知 - if [ -n "${{ secrets.SERVERCHAN_KEY }}" ]; then - echo "{\"title\": \"${TITLE}\", \"desp\": \"${MESSAGE}\"}" > /tmp/serverchan.json - - curl --location "https://sctapi.ftqq.com/${{ secrets.SERVERCHAN_KEY }}.send" \ - --header "Content-Type: application/json" \ - --data @/tmp/serverchan.json \ - --silent > /dev/null - - rm -f /tmp/serverchan.json - echo "📱 部署通知已发送" - fi \ No newline at end of file diff --git a/src/DjangoBlog/.github/workflows/django.yml b/src/DjangoBlog/.github/workflows/django.yml deleted file mode 100644 index ebe7953..0000000 --- a/src/DjangoBlog/.github/workflows/django.yml +++ /dev/null @@ -1,371 +0,0 @@ -name: Django CI - -on: - push: - branches: - - master - - dev - paths-ignore: - - '**/*.md' - - '**/*.css' - - '**/*.js' - pull_request: - branches: - - master - - dev - paths-ignore: - - '**/*.md' - - '**/*.css' - - '**/*.js' - -jobs: - test: - runs-on: ubuntu-latest - strategy: - fail-fast: false - matrix: - include: - # 标准测试 - Python 3.10 - - python-version: "3.10" - test-type: "standard" - database: "mysql" - elasticsearch: false - coverage: false - - # 标准测试 - Python 3.11 - - python-version: "3.11" - test-type: "standard" - database: "mysql" - elasticsearch: false - coverage: false - - # 完整测试 - 包含ES和覆盖率 - - python-version: "3.11" - test-type: "full" - database: "mysql" - elasticsearch: true - coverage: true - - # Docker构建测试 - - python-version: "3.11" - test-type: "docker" - database: "none" - elasticsearch: false - coverage: false - - name: Test (${{ matrix.test-type }}, Python ${{ matrix.python-version }}) - - steps: - - name: Checkout代码 - uses: actions/checkout@v4 - - - name: 设置测试信息 - id: test-info - run: | - echo "test_name=${{ matrix.test-type }}-py${{ matrix.python-version }}" >> $GITHUB_OUTPUT - if [ "${{ matrix.test-type }}" = "docker" ]; then - echo "skip_python_setup=true" >> $GITHUB_OUTPUT - else - echo "skip_python_setup=false" >> $GITHUB_OUTPUT - fi - - # MySQL数据库设置 (只有需要数据库的测试才执行) - - name: 启动MySQL数据库 - if: matrix.database == 'mysql' - uses: samin/mysql-action@v1.3 - with: - host port: 3306 - container port: 3306 - character set server: utf8mb4 - collation server: utf8mb4_general_ci - mysql version: latest - mysql root password: root - mysql database: djangoblog - mysql user: root - mysql password: root - - # Elasticsearch设置 (只有完整测试才执行) - - name: 配置系统参数 (ES) - if: matrix.elasticsearch == true - run: | - sudo swapoff -a - sudo sysctl -w vm.swappiness=1 - sudo sysctl -w fs.file-max=262144 - sudo sysctl -w vm.max_map_count=262144 - - - name: 启动Elasticsearch - if: matrix.elasticsearch == true - uses: miyataka/elasticsearch-github-actions@1 - with: - stack-version: '7.12.1' - plugins: 'https://release.infinilabs.com/analysis-ik/stable/elasticsearch-analysis-ik-7.12.1.zip' - - # Python环境设置 (Docker测试跳过) - - name: 设置Python ${{ matrix.python-version }} - if: steps.test-info.outputs.skip_python_setup == 'false' - uses: actions/setup-python@v5 - with: - python-version: ${{ matrix.python-version }} - cache: 'pip' - cache-dependency-path: 'requirements.txt' - - # 多层缓存策略优化 - - name: 缓存Python依赖 - if: steps.test-info.outputs.skip_python_setup == 'false' - uses: actions/cache@v4 - with: - path: | - ~/.cache/pip - .pytest_cache - key: ${{ runner.os }}-python-${{ matrix.python-version }}-${{ hashFiles('requirements.txt') }}-${{ hashFiles('**/pyproject.toml') }} - restore-keys: | - ${{ runner.os }}-python-${{ matrix.python-version }}-${{ hashFiles('requirements.txt') }}- - ${{ runner.os }}-python-${{ matrix.python-version }}- - ${{ runner.os }}-python- - - # Django缓存优化 (测试数据库等) - - name: 缓存Django资源 - if: matrix.test-type != 'docker' - uses: actions/cache@v4 - with: - path: | - .coverage* - htmlcov/ - .django_cache/ - key: ${{ runner.os }}-django-${{ matrix.test-type }}-${{ github.sha }} - restore-keys: | - ${{ runner.os }}-django-${{ matrix.test-type }}- - ${{ runner.os }}-django- - - - name: 安装Python依赖 - if: steps.test-info.outputs.skip_python_setup == 'false' - run: | - echo "📦 安装Python依赖 (Python ${{ matrix.python-version }})" - python -m pip install --upgrade pip setuptools wheel - - # 安装基础依赖 - pip install -r requirements.txt - - # 根据测试类型安装额外依赖 - if [ "${{ matrix.coverage }}" = "true" ]; then - echo "📊 安装覆盖率工具" - pip install coverage[toml] - fi - - # 验证关键依赖 - echo "🔍 验证关键依赖安装" - python -c "import django; print(f'Django version: {django.get_version()}')" - python -c "import MySQLdb; print('MySQL client: OK')" || python -c "import pymysql; print('PyMySQL client: OK')" - - if [ "${{ matrix.elasticsearch }}" = "true" ]; then - python -c "import elasticsearch; print('Elasticsearch client: OK')" - fi - - # Django环境准备 - - name: 准备Django环境 - if: matrix.test-type != 'docker' - env: - DJANGO_MYSQL_PASSWORD: root - DJANGO_MYSQL_HOST: 127.0.0.1 - DJANGO_ELASTICSEARCH_HOST: ${{ matrix.elasticsearch && '127.0.0.1:9200' || '' }} - run: | - echo "🔧 准备Django测试环境" - - # 等待数据库就绪 - echo "⏳ 等待MySQL数据库启动..." - for i in {1..30}; do - if python -c "import MySQLdb; MySQLdb.connect(host='127.0.0.1', user='root', passwd='root', db='djangoblog')" 2>/dev/null; then - echo "✅ MySQL数据库连接成功" - break - fi - echo "🔄 等待数据库启动... ($i/30)" - sleep 2 - done - - # 等待Elasticsearch就绪 (如果启用) - if [ "${{ matrix.elasticsearch }}" = "true" ]; then - echo "⏳ 等待Elasticsearch启动..." - for i in {1..30}; do - if curl -s http://127.0.0.1:9200/_cluster/health | grep -q '"status":"green"\|"status":"yellow"'; then - echo "✅ Elasticsearch连接成功" - break - fi - echo "🔄 等待Elasticsearch启动... ($i/30)" - sleep 2 - done - fi - - # Django测试执行 - - name: 执行数据库迁移 - if: matrix.test-type != 'docker' - env: - DJANGO_MYSQL_PASSWORD: root - DJANGO_MYSQL_HOST: 127.0.0.1 - DJANGO_ELASTICSEARCH_HOST: ${{ matrix.elasticsearch && '127.0.0.1:9200' || '' }} - run: | - echo "🗄️ 执行数据库迁移" - - # 检查迁移文件 - echo "📋 检查待应用的迁移..." - python manage.py showmigrations - - # 检查是否有未创建的迁移 - python manage.py makemigrations --check --verbosity 2 - - # 执行迁移 - python manage.py migrate --verbosity 2 - - echo "✅ 数据库迁移完成" - - - name: 运行Django测试 - if: matrix.test-type != 'docker' - env: - DJANGO_MYSQL_PASSWORD: root - DJANGO_MYSQL_HOST: 127.0.0.1 - DJANGO_ELASTICSEARCH_HOST: ${{ matrix.elasticsearch && '127.0.0.1:9200' || '' }} - run: | - echo "🧪 开始执行 ${{ matrix.test-type }} 测试 (Python ${{ matrix.python-version }})" - - # 显示Django配置信息 - python manage.py diffsettings | head -20 - - # 运行测试 - if [ "${{ matrix.coverage }}" = "true" ]; then - echo "📊 运行测试并生成覆盖率报告" - coverage run --source='.' --omit='*/venv/*,*/migrations/*,*/tests/*,manage.py' manage.py test --verbosity=2 - - echo "📈 生成覆盖率报告" - coverage xml - coverage report --show-missing - coverage html - - echo "📋 覆盖率统计:" - coverage report | tail -1 - else - echo "🧪 运行标准测试" - python manage.py test --verbosity=2 --failfast - fi - - echo "✅ 测试执行完成" - - # 覆盖率报告上传 (只有完整测试才执行) - - name: 上传覆盖率到Codecov - if: matrix.coverage == true && success() - uses: codecov/codecov-action@v4 - with: - token: ${{ secrets.CODECOV_TOKEN }} - file: ./coverage.xml - flags: unittests - name: codecov-${{ steps.test-info.outputs.test_name }} - fail_ci_if_error: false - verbose: true - - - name: 上传覆盖率到Codecov (备用) - if: matrix.coverage == true && failure() - uses: codecov/codecov-action@v4 - with: - file: ./coverage.xml - flags: unittests - name: codecov-${{ steps.test-info.outputs.test_name }}-fallback - fail_ci_if_error: false - verbose: true - - # Docker构建测试 - - name: 设置QEMU - if: matrix.test-type == 'docker' - uses: docker/setup-qemu-action@v3 - - - name: 设置Docker Buildx - if: matrix.test-type == 'docker' - uses: docker/setup-buildx-action@v3 - - - name: Docker构建测试 - if: matrix.test-type == 'docker' - uses: docker/build-push-action@v5 - with: - context: . - push: false - tags: djangoblog/djangoblog:test-${{ github.sha }} - cache-from: type=gha - cache-to: type=gha,mode=max - - # 收集测试工件 (失败时收集调试信息) - - name: 收集测试工件 - if: failure() && matrix.test-type != 'docker' - run: | - echo "🔍 收集测试失败的调试信息" - - # 收集Django日志 - if [ -d "logs" ]; then - echo "📄 Django日志文件:" - ls -la logs/ - if [ -f "logs/djangoblog.log" ]; then - echo "🔍 最新日志内容:" - tail -100 logs/djangoblog.log - fi - fi - - # 显示数据库状态 - echo "🗄️ 数据库连接状态:" - python -c " - try: - from django.db import connection - cursor = connection.cursor() - cursor.execute('SELECT VERSION()') - print(f'MySQL版本: {cursor.fetchone()[0]}') - cursor.execute('SHOW TABLES') - tables = cursor.fetchall() - print(f'数据库表数量: {len(tables)}') - except Exception as e: - print(f'数据库连接错误: {e}') - " || true - - # Elasticsearch状态 (如果启用) - if [ "${{ matrix.elasticsearch }}" = "true" ]; then - echo "🔍 Elasticsearch状态:" - curl -s http://127.0.0.1:9200/_cluster/health?pretty || true - fi - - # 上传测试工件 - - name: 上传覆盖率HTML报告 - if: matrix.coverage == true && always() - uses: actions/upload-artifact@v4 - with: - name: coverage-report-${{ steps.test-info.outputs.test_name }} - path: htmlcov/ - retention-days: 30 - - # 性能统计 - - name: 测试性能统计 - if: always() && matrix.test-type != 'docker' - run: | - echo "⚡ 测试性能统计:" - echo " 开始时间: $(date -d '@${{ job.started_at }}' '+%Y-%m-%d %H:%M:%S' 2>/dev/null || echo '未知')" - echo " 当前时间: $(date '+%Y-%m-%d %H:%M:%S')" - - # 系统资源使用情况 - echo "💻 系统资源:" - echo " CPU使用: $(top -bn1 | grep "Cpu(s)" | awk '{print $2}' | cut -d'%' -f1)%" - echo " 内存使用: $(free -h | awk '/^Mem:/ {printf "%.1f%%", $3/$2 * 100}')" - echo " 磁盘使用: $(df -h / | awk 'NR==2{printf "%s", $5}')" - - # 测试结果汇总 - - name: 测试完成总结 - if: always() - run: | - echo "📋 ============ 测试执行总结 ============" - echo " 🏷️ 测试类型: ${{ matrix.test-type }}" - echo " 🐍 Python版本: ${{ matrix.python-version }}" - echo " 🗄️ 数据库: ${{ matrix.database }}" - echo " 🔍 Elasticsearch: ${{ matrix.elasticsearch }}" - echo " 📊 覆盖率: ${{ matrix.coverage }}" - echo " ⚡ 状态: ${{ job.status }}" - echo " 📅 完成时间: $(date '+%Y-%m-%d %H:%M:%S')" - echo "============================================" - - # 根据测试结果显示不同消息 - if [ "${{ job.status }}" = "success" ]; then - echo "🎉 测试执行成功!" - else - echo "❌ 测试执行失败,请检查上面的日志" - fi diff --git a/src/DjangoBlog/.github/workflows/docker.yml b/src/DjangoBlog/.github/workflows/docker.yml deleted file mode 100644 index 904fef5..0000000 --- a/src/DjangoBlog/.github/workflows/docker.yml +++ /dev/null @@ -1,43 +0,0 @@ -name: docker - -on: - push: - paths-ignore: - - '**/*.md' - - '**/*.yml' - branches: - - 'master' - - 'dev' - -jobs: - docker: - runs-on: ubuntu-latest - steps: - - name: Set env to docker dev tag - if: endsWith(github.ref, '/dev') - run: | - echo "DOCKER_TAG=test" >> $GITHUB_ENV - - name: Set env to docker latest tag - if: endsWith(github.ref, '/master') - run: | - echo "DOCKER_TAG=latest" >> $GITHUB_ENV - - name: Checkout - uses: actions/checkout@v4 - - name: Set up QEMU - uses: docker/setup-qemu-action@v3 - - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v3 - - - name: Login to DockerHub - uses: docker/login-action@v3 - with: - username: ${{ secrets.DOCKERHUB_USERNAME }} - password: ${{ secrets.DOCKERHUB_TOKEN }} - - name: Build and push - uses: docker/build-push-action@v5 - with: - context: . - push: true - tags: ${{ secrets.DOCKERHUB_USERNAME }}/djangoblog:${{env.DOCKER_TAG}} - - diff --git a/src/DjangoBlog/.github/workflows/publish-release.yml b/src/DjangoBlog/.github/workflows/publish-release.yml deleted file mode 100644 index 5eb0853..0000000 --- a/src/DjangoBlog/.github/workflows/publish-release.yml +++ /dev/null @@ -1,39 +0,0 @@ -name: publish release - -on: - release: - types: [ published ] - -jobs: - docker: - runs-on: ubuntu-latest - steps: - - name: Checkout - uses: actions/checkout@v3 - - - name: Docker meta - id: meta - uses: docker/metadata-action@v3 - with: - images: name/app - - name: Set up QEMU - uses: docker/setup-qemu-action@v2 - - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v2 - - name: Login to DockerHub - uses: docker/login-action@v2 - with: - username: ${{ secrets.DOCKERHUB_USERNAME }} - password: ${{ secrets.DOCKERHUB_TOKEN }} - - name: Build and push - uses: docker/build-push-action@v3 - with: - context: . - push: true - platforms: | - linux/amd64 - linux/arm64 - linux/arm/v7 - linux/arm/v6 - linux/386 - tags: ${{ secrets.DOCKERHUB_USERNAME }}/djangoblog:${{ github.event.release.tag_name }} diff --git a/src/DjangoBlog/.gitignore b/src/DjangoBlog/.gitignore deleted file mode 100644 index 76302b1..0000000 --- a/src/DjangoBlog/.gitignore +++ /dev/null @@ -1,79 +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/ diff --git a/src/DjangoBlog/Dockerfile b/src/DjangoBlog/Dockerfile deleted file mode 100644 index 80b46ac..0000000 --- a/src/DjangoBlog/Dockerfile +++ /dev/null @@ -1,15 +0,0 @@ -FROM python:3.11 -ENV PYTHONUNBUFFERED 1 -WORKDIR /code/djangoblog/ -RUN apt-get update && \ - apt-get install default-libmysqlclient-dev gettext -y && \ - rm -rf /var/lib/apt/lists/* -ADD requirements.txt requirements.txt -RUN pip install --upgrade pip && \ - pip install --no-cache-dir -r requirements.txt && \ - pip install --no-cache-dir gunicorn[gevent] && \ - pip cache purge - -ADD . . -RUN chmod +x /code/djangoblog/deploy/entrypoint.sh -ENTRYPOINT ["/code/djangoblog/deploy/entrypoint.sh"] diff --git a/src/DjangoBlog/LICENSE b/src/DjangoBlog/LICENSE deleted file mode 100644 index 3b08474..0000000 --- a/src/DjangoBlog/LICENSE +++ /dev/null @@ -1,20 +0,0 @@ -The MIT License (MIT) - -Copyright (c) 2025 车亮亮 - -Permission is hereby granted, free of charge, to any person obtaining a copy of -this software and associated documentation files (the "Software"), to deal in -the Software without restriction, including without limitation the rights to -use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of -the Software, and to permit persons to whom the Software is furnished to do so, -subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS -FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR -COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER -IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN -CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. \ No newline at end of file diff --git a/src/DjangoBlog/README.md b/src/DjangoBlog/README.md deleted file mode 100644 index 56aa4cc..0000000 --- a/src/DjangoBlog/README.md +++ /dev/null @@ -1,158 +0,0 @@ -# DjangoBlog - -

- Django CI - CodeQL - codecov - license -

- -

- 一款功能强大、设计优雅的现代化博客系统 -
- English简体中文 -

- ---- - -DjangoBlog 是一款基于 Python 3.10 和 Django 4.0 构建的高性能博客平台。它不仅提供了传统博客的所有核心功能,还通过一个灵活的插件系统,让您可以轻松扩展和定制您的网站。无论您是个人博主、技术爱好者还是内容创作者,DjangoBlog 都旨在为您提供一个稳定、高效且易于维护的写作和发布环境。 - -## ✨ 特性亮点 - -- **强大的内容管理**: 支持文章、独立页面、分类和标签的完整管理。内置强大的 Markdown 编辑器,支持代码语法高亮。 -- **全文搜索**: 集成搜索引擎,提供快速、精准的文章内容搜索。 -- **互动评论系统**: 支持回复、邮件提醒等功能,评论内容同样支持 Markdown。 -- **灵活的侧边栏**: 可自定义展示最新文章、最多阅读、标签云等模块。 -- **社交化登录**: 内置 OAuth 支持,已集成 Google, GitHub, Facebook, 微博, QQ 等主流平台。 -- **高性能缓存**: 原生支持 Redis 缓存,并提供自动刷新机制,确保网站高速响应。 -- **SEO 友好**: 具备基础 SEO 功能,新内容发布后可自动通知 Google 和百度。 -- **便捷的插件系统**: 通过创建独立的插件来扩展博客功能,代码解耦,易于维护。我们已经通过插件实现了文章浏览计数、SEO 优化等功能! -- **集成图床**: 内置简单的图床功能,方便图片上传和管理。 -- **自动化前端**: 集成 `django-compressor`,自动压缩和优化 CSS 及 JavaScript 文件。 -- **健壮的运维**: 内置网站异常邮件提醒和微信公众号管理功能。 - -## 🛠️ 技术栈 - -- **后端**: Python 3.10, Django 4.0 -- **数据库**: MySQL, SQLite (可配置) -- **缓存**: Redis -- **前端**: HTML5, CSS3, JavaScript -- **搜索**: Whoosh, Elasticsearch (可配置) -- **编辑器**: Markdown (mdeditor) - -## 🚀 快速开始 - -### 1. 环境准备 - -确保您的系统中已安装 Python 3.10+ 和 MySQL/MariaDB。 - -### 2. 克隆与安装 - -```bash -# 克隆项目到本地 -git clone https://github.com/liangliangyy/DjangoBlog.git -cd DjangoBlog - -# 安装依赖 -pip install -r requirements.txt -``` - -### 3. 项目配置 - -- **数据库**: - 打开 `djangoblog/settings.py` 文件,找到 `DATABASES` 配置项,修改为您的 MySQL 连接信息。 - - ```python - DATABASES = { - 'default': { - 'ENGINE': 'django.db.backends.mysql', - 'NAME': 'djangoblog', - 'USER': 'root', - 'PASSWORD': 'your_password', - 'HOST': '127.0.0.1', - 'PORT': 3306, - } - } - ``` - 在 MySQL 中创建数据库: - ```sql - CREATE DATABASE `djangoblog` DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; - ``` - -- **更多配置**: - 关于邮件发送、OAuth 登录、缓存等更多高级配置,请参阅我们的 [详细配置文档](/docs/config.md)。 - -### 4. 初始化数据库 - -```bash -python manage.py makemigrations -python manage.py migrate - -# 创建一个超级管理员账户 -python manage.py createsuperuser -``` - -### 5. 运行项目 - -```bash -# (可选) 生成一些测试数据 -python manage.py create_testdata - -# (可选) 收集和压缩静态文件 -python manage.py collectstatic --noinput -python manage.py compress --force - -# 启动开发服务器 -python manage.py runserver -``` - -现在,在您的浏览器中访问 `http://127.0.0.1:8000/`,您应该能看到 DjangoBlog 的首页了! - -## 部署 - -- **传统部署**: 我们为您准备了非常详细的 [服务器部署教程](https://www.lylinux.net/article/2019/8/5/58.html)。 -- **Docker 部署**: 项目已全面支持 Docker。如果您熟悉容器化技术,请参考 [Docker 部署文档](/docs/docker.md) 来快速启动。 -- **Kubernetes 部署**: 我们也提供了完整的 [Kubernetes 部署指南](/docs/k8s.md),助您轻松上云。 - -## 🧩 插件系统 - -插件系统是 DjangoBlog 的核心特色之一。它允许您在不修改核心代码的情况下,通过编写独立的插件来为您的博客添加新功能。 - -- **工作原理**: 插件通过在预定义的“钩子”上注册回调函数来工作。例如,当一篇文章被渲染时,`after_article_body_get` 钩子会被触发,所有注册到此钩子的函数都会被执行。 -- **现有插件**: `view_count`(浏览计数), `seo_optimizer`(SEO优化)等都是通过插件系统实现的。 -- **开发您自己的插件**: 只需在 `plugins` 目录下创建一个新的文件夹,并编写您的 `plugin.py`。欢迎探索并为 DjangoBlog 社区贡献您的创意! - -## 🤝 贡献指南 - -我们热烈欢迎任何形式的贡献!如果您有好的想法或发现了 Bug,请随时提交 Issue 或 Pull Request。 - -## 📄 许可证 - -本项目基于 [MIT License](LICENSE) 开源。 - ---- - -## ❤️ 支持与赞助 - -如果您觉得这个项目对您有帮助,并且希望支持我继续维护和开发新功能,欢迎请我喝杯咖啡!您的每一份支持都是我前进的最大动力。 - -

- 支付宝赞助 - 微信赞助 -

-

- (左) 支付宝 / (右) 微信 -

- -## 🙏 鸣谢 - -特别感谢 **JetBrains** 为本项目提供的免费开源许可证。 - -

- - JetBrains Logo - -

- ---- -> 如果本项目帮助到了你,请在[这里](https://github.com/liangliangyy/DjangoBlog/issues/214)留下你的网址,让更多的人看到。您的回复将会是我继续更新维护下去的动力。 diff --git a/src/DjangoBlog/accounts/__init__.py b/src/DjangoBlog/accounts/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/src/DjangoBlog/accounts/admin.py b/src/DjangoBlog/accounts/admin.py deleted file mode 100644 index 29d162a..0000000 --- a/src/DjangoBlog/accounts/admin.py +++ /dev/null @@ -1,60 +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 _ - -# 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',) - search_fields = ('username', 'nickname', 'email') diff --git a/src/DjangoBlog/accounts/apps.py b/src/DjangoBlog/accounts/apps.py deleted file mode 100644 index 9b3fc5a..0000000 --- a/src/DjangoBlog/accounts/apps.py +++ /dev/null @@ -1,5 +0,0 @@ -from django.apps import AppConfig - - -class AccountsConfig(AppConfig): - name = 'accounts' diff --git a/src/DjangoBlog/accounts/forms.py b/src/DjangoBlog/accounts/forms.py deleted file mode 100644 index fce4137..0000000 --- a/src/DjangoBlog/accounts/forms.py +++ /dev/null @@ -1,117 +0,0 @@ -from django import forms -from django.contrib.auth import get_user_model, password_validation -from django.contrib.auth.forms import AuthenticationForm, UserCreationForm -from django.core.exceptions import ValidationError -from django.forms import widgets -from django.utils.translation import gettext_lazy as _ -from . import utils -from .models import BlogUser - - -class LoginForm(AuthenticationForm): - def __init__(self, *args, **kwargs): - super(LoginForm, self).__init__(*args, **kwargs) - self.fields['username'].widget = widgets.TextInput( - attrs={'placeholder': "username", "class": "form-control"}) - self.fields['password'].widget = widgets.PasswordInput( - attrs={'placeholder': "password", "class": "form-control"}) - - -class RegisterForm(UserCreationForm): - def __init__(self, *args, **kwargs): - super(RegisterForm, self).__init__(*args, **kwargs) - - self.fields['username'].widget = widgets.TextInput( - attrs={'placeholder': "username", "class": "form-control"}) - self.fields['email'].widget = widgets.EmailInput( - attrs={'placeholder': "email", "class": "form-control"}) - self.fields['password1'].widget = widgets.PasswordInput( - attrs={'placeholder': "password", "class": "form-control"}) - self.fields['password2'].widget = widgets.PasswordInput( - attrs={'placeholder': "repeat password", "class": "form-control"}) - - def clean_email(self): - email = self.cleaned_data['email'] - if get_user_model().objects.filter(email=email).exists(): - raise ValidationError(_("email already exists")) - return email - - class Meta: - model = get_user_model() - fields = ("username", "email") - - -class ForgetPasswordForm(forms.Form): - new_password1 = forms.CharField( - label=_("New password"), - widget=forms.PasswordInput( - attrs={ - "class": "form-control", - 'placeholder': _("New password") - } - ), - ) - - new_password2 = forms.CharField( - label="确认密码", - widget=forms.PasswordInput( - attrs={ - "class": "form-control", - 'placeholder': _("Confirm password") - } - ), - ) - - email = forms.EmailField( - label='邮箱', - widget=forms.TextInput( - attrs={ - 'class': 'form-control', - 'placeholder': _("Email") - } - ), - ) - - code = forms.CharField( - label=_('Code'), - widget=forms.TextInput( - attrs={ - 'class': 'form-control', - 'placeholder': _("Code") - } - ), - ) - - def clean_new_password2(self): - password1 = self.data.get("new_password1") - password2 = self.data.get("new_password2") - if password1 and password2 and password1 != password2: - raise ValidationError(_("passwords do not match")) - password_validation.validate_password(password2) - - return password2 - - def clean_email(self): - user_email = self.cleaned_data.get("email") - if not BlogUser.objects.filter( - email=user_email - ).exists(): - # todo 这里的报错提示可以判断一个邮箱是不是注册过,如果不想暴露可以修改 - raise ValidationError(_("email does not exist")) - return user_email - - def clean_code(self): - code = self.cleaned_data.get("code") - error = utils.verify( - email=self.cleaned_data.get("email"), - code=code, - ) - if error: - raise ValidationError(error) - return code - - -class ForgetPasswordCodeForm(forms.Form): - email = forms.EmailField( - label=_('Email'), - ) diff --git a/src/DjangoBlog/accounts/migrations/0001_initial.py b/src/DjangoBlog/accounts/migrations/0001_initial.py deleted file mode 100644 index d2fbcab..0000000 --- a/src/DjangoBlog/accounts/migrations/0001_initial.py +++ /dev/null @@ -1,49 +0,0 @@ -# Generated by Django 4.1.7 on 2023-03-02 07:14 - -import django.contrib.auth.models -import django.contrib.auth.validators -from django.db import migrations, models -import django.utils.timezone - - -class Migration(migrations.Migration): - - initial = True - - dependencies = [ - ('auth', '0012_alter_user_first_name_max_length'), - ] - - operations = [ - migrations.CreateModel( - name='BlogUser', - fields=[ - ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('password', models.CharField(max_length=128, verbose_name='password')), - ('last_login', models.DateTimeField(blank=True, null=True, verbose_name='last login')), - ('is_superuser', models.BooleanField(default=False, help_text='Designates that this user has all permissions without explicitly assigning them.', verbose_name='superuser status')), - ('username', models.CharField(error_messages={'unique': 'A user with that username already exists.'}, help_text='Required. 150 characters or fewer. Letters, digits and @/./+/-/_ only.', max_length=150, unique=True, validators=[django.contrib.auth.validators.UnicodeUsernameValidator()], verbose_name='username')), - ('first_name', models.CharField(blank=True, max_length=150, verbose_name='first name')), - ('last_name', models.CharField(blank=True, max_length=150, verbose_name='last name')), - ('email', models.EmailField(blank=True, max_length=254, verbose_name='email address')), - ('is_staff', models.BooleanField(default=False, help_text='Designates whether the user can log into this admin site.', verbose_name='staff status')), - ('is_active', models.BooleanField(default=True, help_text='Designates whether this user should be treated as active. Unselect this instead of deleting accounts.', verbose_name='active')), - ('date_joined', models.DateTimeField(default=django.utils.timezone.now, verbose_name='date joined')), - ('nickname', models.CharField(blank=True, max_length=100, verbose_name='昵称')), - ('created_time', models.DateTimeField(default=django.utils.timezone.now, verbose_name='创建时间')), - ('last_mod_time', models.DateTimeField(default=django.utils.timezone.now, verbose_name='修改时间')), - ('source', models.CharField(blank=True, max_length=100, verbose_name='创建来源')), - ('groups', models.ManyToManyField(blank=True, help_text='The groups this user belongs to. A user will get all permissions granted to each of their groups.', related_name='user_set', related_query_name='user', to='auth.group', verbose_name='groups')), - ('user_permissions', models.ManyToManyField(blank=True, help_text='Specific permissions for this user.', related_name='user_set', related_query_name='user', to='auth.permission', verbose_name='user permissions')), - ], - options={ - 'verbose_name': '用户', - 'verbose_name_plural': '用户', - 'ordering': ['-id'], - 'get_latest_by': 'id', - }, - managers=[ - ('objects', django.contrib.auth.models.UserManager()), - ], - ), - ] diff --git a/src/DjangoBlog/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 deleted file mode 100644 index 1a9f509..0000000 --- a/src/DjangoBlog/accounts/migrations/0002_alter_bloguser_options_remove_bloguser_created_time_and_more.py +++ /dev/null @@ -1,46 +0,0 @@ -# Generated by Django 4.2.5 on 2023-09-06 13:13 - -from django.db import migrations, models -import django.utils.timezone - - -class Migration(migrations.Migration): - - dependencies = [ - ('accounts', '0001_initial'), - ] - - operations = [ - migrations.AlterModelOptions( - name='bloguser', - options={'get_latest_by': 'id', 'ordering': ['-id'], 'verbose_name': 'user', 'verbose_name_plural': 'user'}, - ), - migrations.RemoveField( - model_name='bloguser', - name='created_time', - ), - migrations.RemoveField( - model_name='bloguser', - name='last_mod_time', - ), - migrations.AddField( - model_name='bloguser', - name='creation_time', - field=models.DateTimeField(default=django.utils.timezone.now, verbose_name='creation time'), - ), - migrations.AddField( - model_name='bloguser', - name='last_modify_time', - field=models.DateTimeField(default=django.utils.timezone.now, verbose_name='last modify time'), - ), - migrations.AlterField( - model_name='bloguser', - name='nickname', - field=models.CharField(blank=True, max_length=100, verbose_name='nick name'), - ), - migrations.AlterField( - model_name='bloguser', - name='source', - field=models.CharField(blank=True, max_length=100, verbose_name='create source'), - ), - ] diff --git a/src/DjangoBlog/accounts/migrations/__init__.py b/src/DjangoBlog/accounts/migrations/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/src/DjangoBlog/accounts/models.py b/src/DjangoBlog/accounts/models.py deleted file mode 100644 index 3baddbb..0000000 --- a/src/DjangoBlog/accounts/models.py +++ /dev/null @@ -1,35 +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 - - -# 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/DjangoBlog/accounts/templatetags/__init__.py b/src/DjangoBlog/accounts/templatetags/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/src/DjangoBlog/accounts/tests.py b/src/DjangoBlog/accounts/tests.py deleted file mode 100644 index 6893411..0000000 --- a/src/DjangoBlog/accounts/tests.py +++ /dev/null @@ -1,207 +0,0 @@ -from django.test import Client, RequestFactory, TestCase -from django.urls import reverse -from django.utils import timezone -from django.utils.translation import gettext_lazy as _ - -from accounts.models import BlogUser -from blog.models import Article, Category -from djangoblog.utils import * -from . import utils - - -# Create your tests here. - -class AccountTest(TestCase): - def setUp(self): - self.client = Client() - self.factory = RequestFactory() - self.blog_user = BlogUser.objects.create_user( - username="test", - email="admin@admin.com", - password="12345678" - ) - self.new_test = "xxx123--=" - - def test_validate_account(self): - site = get_current_site().domain - user = BlogUser.objects.create_superuser( - email="liangliangyy1@gmail.com", - username="liangliangyy1", - password="qwer!@#$ggg") - testuser = BlogUser.objects.get(username='liangliangyy1') - - loginresult = self.client.login( - username='liangliangyy1', - password='qwer!@#$ggg') - self.assertEqual(loginresult, True) - response = self.client.get('/admin/') - self.assertEqual(response.status_code, 200) - - category = Category() - category.name = "categoryaaa" - category.creation_time = timezone.now() - category.last_modify_time = timezone.now() - category.save() - - article = Article() - article.title = "nicetitleaaa" - article.body = "nicecontentaaa" - article.author = user - article.category = category - article.type = 'a' - article.status = 'p' - article.save() - - response = self.client.get(article.get_admin_url()) - self.assertEqual(response.status_code, 200) - - def test_validate_register(self): - self.assertEquals( - 0, len( - BlogUser.objects.filter( - email='user123@user.com'))) - response = self.client.post(reverse('account:register'), { - 'username': 'user1233', - 'email': 'user123@user.com', - 'password1': 'password123!q@wE#R$T', - 'password2': 'password123!q@wE#R$T', - }) - self.assertEquals( - 1, len( - BlogUser.objects.filter( - email='user123@user.com'))) - user = BlogUser.objects.filter(email='user123@user.com')[0] - sign = get_sha256(get_sha256(settings.SECRET_KEY + str(user.id))) - path = reverse('accounts:result') - url = '{path}?type=validation&id={id}&sign={sign}'.format( - path=path, id=user.id, sign=sign) - response = self.client.get(url) - self.assertEqual(response.status_code, 200) - - self.client.login(username='user1233', password='password123!q@wE#R$T') - user = BlogUser.objects.filter(email='user123@user.com')[0] - user.is_superuser = True - user.is_staff = True - user.save() - delete_sidebar_cache() - category = Category() - category.name = "categoryaaa" - category.creation_time = timezone.now() - category.last_modify_time = timezone.now() - category.save() - - article = Article() - article.category = category - article.title = "nicetitle333" - article.body = "nicecontentttt" - article.author = user - - article.type = 'a' - article.status = 'p' - article.save() - - response = self.client.get(article.get_admin_url()) - self.assertEqual(response.status_code, 200) - - response = self.client.get(reverse('account:logout')) - self.assertIn(response.status_code, [301, 302, 200]) - - response = self.client.get(article.get_admin_url()) - self.assertIn(response.status_code, [301, 302, 200]) - - response = self.client.post(reverse('account:login'), { - 'username': 'user1233', - 'password': 'password123' - }) - self.assertIn(response.status_code, [301, 302, 200]) - - response = self.client.get(article.get_admin_url()) - self.assertIn(response.status_code, [301, 302, 200]) - - def test_verify_email_code(self): - to_email = "admin@admin.com" - code = generate_code() - utils.set_code(to_email, code) - utils.send_verify_email(to_email, code) - - err = utils.verify("admin@admin.com", code) - self.assertEqual(err, None) - - err = utils.verify("admin@123.com", code) - self.assertEqual(type(err), str) - - def test_forget_password_email_code_success(self): - resp = self.client.post( - path=reverse("account:forget_password_code"), - data=dict(email="admin@admin.com") - ) - - self.assertEqual(resp.status_code, 200) - self.assertEqual(resp.content.decode("utf-8"), "ok") - - def test_forget_password_email_code_fail(self): - resp = self.client.post( - path=reverse("account:forget_password_code"), - data=dict() - ) - self.assertEqual(resp.content.decode("utf-8"), "错误的邮箱") - - resp = self.client.post( - path=reverse("account:forget_password_code"), - data=dict(email="admin@com") - ) - self.assertEqual(resp.content.decode("utf-8"), "错误的邮箱") - - def test_forget_password_email_success(self): - code = generate_code() - utils.set_code(self.blog_user.email, code) - data = dict( - new_password1=self.new_test, - new_password2=self.new_test, - email=self.blog_user.email, - code=code, - ) - resp = self.client.post( - path=reverse("account:forget_password"), - data=data - ) - self.assertEqual(resp.status_code, 302) - - # 验证用户密码是否修改成功 - blog_user = BlogUser.objects.filter( - email=self.blog_user.email, - ).first() # type: BlogUser - self.assertNotEqual(blog_user, None) - self.assertEqual(blog_user.check_password(data["new_password1"]), True) - - def test_forget_password_email_not_user(self): - data = dict( - new_password1=self.new_test, - new_password2=self.new_test, - email="123@123.com", - code="123456", - ) - resp = self.client.post( - path=reverse("account:forget_password"), - data=data - ) - - self.assertEqual(resp.status_code, 200) - - - def test_forget_password_email_code_error(self): - code = generate_code() - utils.set_code(self.blog_user.email, code) - data = dict( - new_password1=self.new_test, - new_password2=self.new_test, - email=self.blog_user.email, - code="111111", - ) - resp = self.client.post( - path=reverse("account:forget_password"), - data=data - ) - - self.assertEqual(resp.status_code, 200) - diff --git a/src/DjangoBlog/accounts/urls.py b/src/DjangoBlog/accounts/urls.py deleted file mode 100644 index 107a801..0000000 --- a/src/DjangoBlog/accounts/urls.py +++ /dev/null @@ -1,28 +0,0 @@ -from django.urls import path -from django.urls import re_path - -from . import views -from .forms import LoginForm - -app_name = "accounts" - -urlpatterns = [re_path(r'^login/$', - views.LoginView.as_view(success_url='/'), - name='login', - kwargs={'authentication_form': LoginForm}), - re_path(r'^register/$', - views.RegisterView.as_view(success_url="/"), - name='register'), - re_path(r'^logout/$', - views.LogoutView.as_view(), - name='logout'), - path(r'account/result.html', - views.account_result, - name='result'), - re_path(r'^forget_password/$', - views.ForgetPasswordView.as_view(), - name='forget_password'), - re_path(r'^forget_password_code/$', - views.ForgetPasswordEmailCode.as_view(), - name='forget_password_code'), - ] diff --git a/src/DjangoBlog/accounts/user_login_backend.py b/src/DjangoBlog/accounts/user_login_backend.py deleted file mode 100644 index 73cdca1..0000000 --- a/src/DjangoBlog/accounts/user_login_backend.py +++ /dev/null @@ -1,26 +0,0 @@ -from django.contrib.auth import get_user_model -from django.contrib.auth.backends import ModelBackend - - -class EmailOrUsernameModelBackend(ModelBackend): - """ - 允许使用用户名或邮箱登录 - """ - - def authenticate(self, request, username=None, password=None, **kwargs): - if '@' in username: - kwargs = {'email': username} - else: - kwargs = {'username': username} - try: - user = get_user_model().objects.get(**kwargs) - if user.check_password(password): - return user - except get_user_model().DoesNotExist: - return None - - def get_user(self, username): - try: - return get_user_model().objects.get(pk=username) - except get_user_model().DoesNotExist: - return None diff --git a/src/DjangoBlog/accounts/utils.py b/src/DjangoBlog/accounts/utils.py deleted file mode 100644 index 4b94bdf..0000000 --- a/src/DjangoBlog/accounts/utils.py +++ /dev/null @@ -1,49 +0,0 @@ -import typing -from datetime import timedelta - -from django.core.cache import cache -from django.utils.translation import gettext -from django.utils.translation import gettext_lazy as _ - -from djangoblog.utils import send_email - -_code_ttl = timedelta(minutes=5) - - -def send_verify_email(to_mail: str, code: str, subject: str = _("Verify Email")): - """发送重设密码验证码 - Args: - to_mail: 接受邮箱 - subject: 邮件主题 - code: 验证码 - """ - html_content = _( - "You are resetting the password, the verification code is:%(code)s, valid within 5 minutes, please keep it " - "properly") % {'code': code} - send_email([to_mail], subject, html_content) - - -def verify(email: str, code: str) -> typing.Optional[str]: - """验证code是否有效 - Args: - email: 请求邮箱 - code: 验证码 - Return: - 如果有错误就返回错误str - Node: - 这里的错误处理不太合理,应该采用raise抛出 - 否测调用方也需要对error进行处理 - """ - cache_code = get_code(email) - if cache_code != code: - return gettext("Verification code error") - - -def set_code(email: str, code: str): - """设置code""" - cache.set(email, code, _code_ttl.seconds) - - -def get_code(email: str) -> typing.Optional[str]: - """获取code""" - return cache.get(email) diff --git a/src/DjangoBlog/accounts/views.py b/src/DjangoBlog/accounts/views.py deleted file mode 100644 index ae67aec..0000000 --- a/src/DjangoBlog/accounts/views.py +++ /dev/null @@ -1,204 +0,0 @@ -import logging -from django.utils.translation import gettext_lazy as _ -from django.conf import settings -from django.contrib import auth -from django.contrib.auth import REDIRECT_FIELD_NAME -from django.contrib.auth import get_user_model -from django.contrib.auth import logout -from django.contrib.auth.forms import AuthenticationForm -from django.contrib.auth.hashers import make_password -from django.http import HttpResponseRedirect, HttpResponseForbidden -from django.http.request import HttpRequest -from django.http.response import HttpResponse -from django.shortcuts import get_object_or_404 -from django.shortcuts import render -from django.urls import reverse -from django.utils.decorators import method_decorator -from django.utils.http import url_has_allowed_host_and_scheme -from django.views import View -from django.views.decorators.cache import never_cache -from django.views.decorators.csrf import csrf_protect -from django.views.decorators.debug import sensitive_post_parameters -from django.views.generic import FormView, RedirectView - -from djangoblog.utils import send_email, get_sha256, get_current_site, generate_code, delete_sidebar_cache -from . import utils -from .forms import RegisterForm, LoginForm, ForgetPasswordForm, ForgetPasswordCodeForm -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): - 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) - site = get_current_site().domain - sign = get_sha256(get_sha256(settings.SECRET_KEY + str(user.id))) - - if settings.DEBUG: - site = '127.0.0.1:8000' - path = reverse('account:result') - url = "http://{site}{path}?type=validation&id={id}&sign={sign}".format( - site=site, path=path, id=user.id, sign=sign) - - content = """ -

请点击下面链接验证您的邮箱

- - {url} - - 再次感谢您! -
- 如果上面链接无法打开,请将此链接复制至浏览器。 - {url} - """.format(url=url) - send_email( - emailto=[ - user.email, - ], - title='验证您的电子邮箱', - content=content) - - url = reverse('accounts:result') + \ - '?type=register&id=' + str(user.id) - return HttpResponseRedirect(url) - else: - return self.render_to_response({ - 'form': form - }) - - -class LogoutView(RedirectView): - 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() - return super(LogoutView, self).get(request, *args, **kwargs) - - -class LoginView(FormView): - form_class = LoginForm - template_name = 'account/login.html' - success_url = '/' - redirect_field_name = REDIRECT_FIELD_NAME - login_ttl = 2626560 # 一个月的时间 - - @method_decorator(sensitive_post_parameters('password')) - @method_decorator(csrf_protect) - @method_decorator(never_cache) - def dispatch(self, request, *args, **kwargs): - - return super(LoginView, self).dispatch(request, *args, **kwargs) - - def get_context_data(self, **kwargs): - redirect_to = self.request.GET.get(self.redirect_field_name) - if redirect_to is None: - redirect_to = '/' - kwargs['redirect_to'] = redirect_to - - return super(LoginView, self).get_context_data(**kwargs) - - def form_valid(self, form): - form = AuthenticationForm(data=self.request.POST, request=self.request) - - if form.is_valid(): - 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) - return super(LoginView, self).form_valid(form) - # return HttpResponseRedirect('/') - else: - return self.render_to_response({ - 'form': form - }) - - def get_success_url(self): - - redirect_to = self.request.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: - return HttpResponseRedirect('/') - if type and type in ['register', 'validation']: - if type == 'register': - content = ''' - 恭喜您注册成功,一封验证邮件已经发送到您的邮箱,请验证您的邮箱后登录本站。 - ''' - title = '注册成功' - else: - c_sign = get_sha256(get_sha256(settings.SECRET_KEY + str(user.id))) - sign = request.GET.get('sign') - if sign != c_sign: - return HttpResponseForbidden() - user.is_active = True - user.save() - content = ''' - 恭喜您已经成功的完成邮箱验证,您现在可以使用您的账号来登录本站。 - ''' - title = '验证成功' - return render(request, 'account/result.html', { - 'title': title, - 'content': content - }) - 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.save() - 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) - - return HttpResponse("ok") diff --git a/src/DjangoBlog/blog/__init__.py b/src/DjangoBlog/blog/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/src/DjangoBlog/blog/admin.py b/src/DjangoBlog/blog/admin.py deleted file mode 100644 index 69d7f8e..0000000 --- a/src/DjangoBlog/blog/admin.py +++ /dev/null @@ -1,114 +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 _ - -# Register your models here. -from .models import Article, Category, Tag, Links, SideBar, BlogSettings - - -class ArticleForm(forms.ModelForm): - # body = forms.CharField(widget=AdminPagedownWidget()) - - 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] - 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) - - 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 diff --git a/src/DjangoBlog/blog/apps.py b/src/DjangoBlog/blog/apps.py deleted file mode 100644 index 7930587..0000000 --- a/src/DjangoBlog/blog/apps.py +++ /dev/null @@ -1,5 +0,0 @@ -from django.apps import AppConfig - - -class BlogConfig(AppConfig): - name = 'blog' diff --git a/src/DjangoBlog/blog/context_processors.py b/src/DjangoBlog/blog/context_processors.py deleted file mode 100644 index 73e3088..0000000 --- a/src/DjangoBlog/blog/context_processors.py +++ /dev/null @@ -1,43 +0,0 @@ -import logging - -from django.utils import timezone - -from djangoblog.utils import cache, get_blog_setting -from .models import Category, Article - -logger = logging.getLogger(__name__) - - -def seo_processor(requests): - key = 'seo_processor' - value = cache.get(key) - if value: - return value - else: - logger.info('set processor cache.') - setting = get_blog_setting() - value = { - 'SITE_NAME': setting.site_name, - 'SHOW_GOOGLE_ADSENSE': setting.show_google_adsense, - 'GOOGLE_ADSENSE_CODES': setting.google_adsense_codes, - 'SITE_SEO_DESCRIPTION': setting.site_seo_description, - 'SITE_DESCRIPTION': setting.site_description, - 'SITE_KEYWORDS': setting.site_keywords, - 'SITE_BASE_URL': requests.scheme + '://' + requests.get_host() + '/', - 'ARTICLE_SUB_LENGTH': setting.article_sub_length, - 'nav_category_list': Category.objects.all(), - 'nav_pages': Article.objects.filter( - type='p', - status='p'), - 'OPEN_SITE_COMMENT': setting.open_site_comment, - 'BEIAN_CODE': setting.beian_code, - 'ANALYTICS_CODE': setting.analytics_code, - "BEIAN_CODE_GONGAN": setting.gongan_beiancode, - "SHOW_GONGAN_CODE": setting.show_gongan_code, - "CURRENT_YEAR": timezone.now().year, - "GLOBAL_HEADER": setting.global_header, - "GLOBAL_FOOTER": setting.global_footer, - "COMMENT_NEED_REVIEW": setting.comment_need_review, - } - cache.set(key, value, 60 * 60 * 10) - return value diff --git a/src/DjangoBlog/blog/documents.py b/src/DjangoBlog/blog/documents.py deleted file mode 100644 index 0f1db7b..0000000 --- a/src/DjangoBlog/blog/documents.py +++ /dev/null @@ -1,213 +0,0 @@ -import time - -import elasticsearch.client -from django.conf import settings -from elasticsearch_dsl import Document, InnerDoc, Date, Integer, Long, Text, Object, GeoPoint, Keyword, Boolean -from elasticsearch_dsl.connections import connections - -from blog.models import Article - -ELASTICSEARCH_ENABLED = hasattr(settings, 'ELASTICSEARCH_DSL') - -if ELASTICSEARCH_ENABLED: - connections.create_connection( - hosts=[settings.ELASTICSEARCH_DSL['default']['hosts']]) - from elasticsearch import Elasticsearch - - es = Elasticsearch(settings.ELASTICSEARCH_DSL['default']['hosts']) - from elasticsearch.client import IngestClient - - c = IngestClient(es) - try: - c.get_pipeline('geoip') - except elasticsearch.exceptions.NotFoundError: - c.put_pipeline('geoip', body='''{ - "description" : "Add geoip info", - "processors" : [ - { - "geoip" : { - "field" : "ip" - } - } - ] - }''') - - -class GeoIp(InnerDoc): - continent_name = Keyword() - country_iso_code = Keyword() - country_name = Keyword() - location = GeoPoint() - - -class UserAgentBrowser(InnerDoc): - Family = Keyword() - Version = Keyword() - - -class UserAgentOS(UserAgentBrowser): - pass - - -class UserAgentDevice(InnerDoc): - Family = Keyword() - Brand = Keyword() - Model = Keyword() - - -class UserAgent(InnerDoc): - browser = Object(UserAgentBrowser, required=False) - os = Object(UserAgentOS, required=False) - device = Object(UserAgentDevice, required=False) - string = Text() - is_bot = Boolean() - - -class ElapsedTimeDocument(Document): - url = Keyword() - time_taken = Long() - log_datetime = Date() - ip = Keyword() - geoip = Object(GeoIp, required=False) - useragent = Object(UserAgent, required=False) - - class Index: - name = 'performance' - settings = { - "number_of_shards": 1, - "number_of_replicas": 0 - } - - class Meta: - doc_type = 'ElapsedTime' - - -class ElaspedTimeDocumentManager: - @staticmethod - def build_index(): - from elasticsearch import Elasticsearch - client = Elasticsearch(settings.ELASTICSEARCH_DSL['default']['hosts']) - res = client.indices.exists(index="performance") - if not res: - ElapsedTimeDocument.init() - - @staticmethod - def delete_index(): - from elasticsearch import Elasticsearch - es = Elasticsearch(settings.ELASTICSEARCH_DSL['default']['hosts']) - es.indices.delete(index='performance', ignore=[400, 404]) - - @staticmethod - def create(url, time_taken, log_datetime, useragent, ip): - ElaspedTimeDocumentManager.build_index() - ua = UserAgent() - ua.browser = UserAgentBrowser() - ua.browser.Family = useragent.browser.family - ua.browser.Version = useragent.browser.version_string - - ua.os = UserAgentOS() - ua.os.Family = useragent.os.family - ua.os.Version = useragent.os.version_string - - ua.device = UserAgentDevice() - ua.device.Family = useragent.device.family - ua.device.Brand = useragent.device.brand - ua.device.Model = useragent.device.model - ua.string = useragent.ua_string - ua.is_bot = useragent.is_bot - - doc = ElapsedTimeDocument( - meta={ - 'id': int( - round( - time.time() * - 1000)) - }, - url=url, - time_taken=time_taken, - log_datetime=log_datetime, - useragent=ua, ip=ip) - doc.save(pipeline="geoip") - - -class ArticleDocument(Document): - body = Text(analyzer='ik_max_word', search_analyzer='ik_smart') - title = Text(analyzer='ik_max_word', search_analyzer='ik_smart') - author = Object(properties={ - 'nickname': Text(analyzer='ik_max_word', search_analyzer='ik_smart'), - 'id': Integer() - }) - category = Object(properties={ - 'name': Text(analyzer='ik_max_word', search_analyzer='ik_smart'), - 'id': Integer() - }) - tags = Object(properties={ - 'name': Text(analyzer='ik_max_word', search_analyzer='ik_smart'), - 'id': Integer() - }) - - pub_time = Date() - status = Text() - comment_status = Text() - type = Text() - views = Integer() - article_order = Integer() - - class Index: - name = 'blog' - settings = { - "number_of_shards": 1, - "number_of_replicas": 0 - } - - class Meta: - doc_type = 'Article' - - -class ArticleDocumentManager(): - - def __init__(self): - self.create_index() - - def create_index(self): - ArticleDocument.init() - - def delete_index(self): - from elasticsearch import Elasticsearch - es = Elasticsearch(settings.ELASTICSEARCH_DSL['default']['hosts']) - es.indices.delete(index='blog', ignore=[400, 404]) - - def convert_to_doc(self, articles): - return [ - ArticleDocument( - meta={ - 'id': article.id}, - body=article.body, - title=article.title, - author={ - 'nickname': article.author.username, - 'id': article.author.id}, - category={ - 'name': article.category.name, - 'id': article.category.id}, - tags=[ - { - 'name': t.name, - 'id': t.id} for t in article.tags.all()], - pub_time=article.pub_time, - status=article.status, - comment_status=article.comment_status, - type=article.type, - views=article.views, - article_order=article.article_order) for article in articles] - - def rebuild(self, articles=None): - ArticleDocument.init() - articles = articles if articles else Article.objects.all() - docs = self.convert_to_doc(articles) - for doc in docs: - doc.save() - - def update_docs(self, docs): - for doc in docs: - doc.save() diff --git a/src/DjangoBlog/blog/forms.py b/src/DjangoBlog/blog/forms.py deleted file mode 100644 index 715be76..0000000 --- a/src/DjangoBlog/blog/forms.py +++ /dev/null @@ -1,19 +0,0 @@ -import logging - -from django import forms -from haystack.forms import SearchForm - -logger = logging.getLogger(__name__) - - -class BlogSearchForm(SearchForm): - querydata = forms.CharField(required=True) - - def search(self): - datas = super(BlogSearchForm, self).search() - if not self.is_valid(): - return self.no_query_found() - - if self.cleaned_data['querydata']: - logger.info(self.cleaned_data['querydata']) - return datas diff --git a/src/DjangoBlog/blog/management/__init__.py b/src/DjangoBlog/blog/management/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/src/DjangoBlog/blog/management/commands/__init__.py b/src/DjangoBlog/blog/management/commands/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/src/DjangoBlog/blog/management/commands/build_index.py b/src/DjangoBlog/blog/management/commands/build_index.py deleted file mode 100644 index 3c4acd7..0000000 --- a/src/DjangoBlog/blog/management/commands/build_index.py +++ /dev/null @@ -1,18 +0,0 @@ -from django.core.management.base import BaseCommand - -from blog.documents import ElapsedTimeDocument, ArticleDocumentManager, ElaspedTimeDocumentManager, \ - ELASTICSEARCH_ENABLED - - -# TODO 参数化 -class Command(BaseCommand): - help = 'build search index' - - def handle(self, *args, **options): - if ELASTICSEARCH_ENABLED: - ElaspedTimeDocumentManager.build_index() - manager = ElapsedTimeDocument() - manager.init() - manager = ArticleDocumentManager() - manager.delete_index() - manager.rebuild() diff --git a/src/DjangoBlog/blog/management/commands/build_search_words.py b/src/DjangoBlog/blog/management/commands/build_search_words.py deleted file mode 100644 index cfe7e0d..0000000 --- a/src/DjangoBlog/blog/management/commands/build_search_words.py +++ /dev/null @@ -1,13 +0,0 @@ -from django.core.management.base import BaseCommand - -from blog.models import Tag, Category - - -# TODO 参数化 -class Command(BaseCommand): - help = 'build search words' - - def handle(self, *args, **options): - datas = set([t.name for t in Tag.objects.all()] + - [t.name for t in Category.objects.all()]) - print('\n'.join(datas)) diff --git a/src/DjangoBlog/blog/management/commands/clear_cache.py b/src/DjangoBlog/blog/management/commands/clear_cache.py deleted file mode 100644 index 0d66172..0000000 --- a/src/DjangoBlog/blog/management/commands/clear_cache.py +++ /dev/null @@ -1,11 +0,0 @@ -from django.core.management.base import BaseCommand - -from djangoblog.utils import cache - - -class Command(BaseCommand): - help = 'clear the whole cache' - - def handle(self, *args, **options): - cache.clear() - self.stdout.write(self.style.SUCCESS('Cleared cache\n')) diff --git a/src/DjangoBlog/blog/management/commands/create_testdata.py b/src/DjangoBlog/blog/management/commands/create_testdata.py deleted file mode 100644 index 675d2ba..0000000 --- a/src/DjangoBlog/blog/management/commands/create_testdata.py +++ /dev/null @@ -1,40 +0,0 @@ -from django.contrib.auth import get_user_model -from django.contrib.auth.hashers import make_password -from django.core.management.base import BaseCommand - -from blog.models import Article, Tag, Category - - -class Command(BaseCommand): - help = 'create test datas' - - def handle(self, *args, **options): - user = get_user_model().objects.get_or_create( - email='test@test.com', username='测试用户', password=make_password('test!q@w#eTYU'))[0] - - pcategory = Category.objects.get_or_create( - name='我是父类目', parent_category=None)[0] - - category = Category.objects.get_or_create( - name='子类目', parent_category=pcategory)[0] - - category.save() - basetag = Tag() - basetag.name = "标签" - basetag.save() - for i in range(1, 20): - article = Article.objects.get_or_create( - category=category, - title='nice title ' + str(i), - body='nice content ' + str(i), - author=user)[0] - tag = Tag() - tag.name = "标签" + str(i) - tag.save() - article.tags.add(tag) - article.tags.add(basetag) - article.save() - - from djangoblog.utils import cache - cache.clear() - self.stdout.write(self.style.SUCCESS('created test datas \n')) diff --git a/src/DjangoBlog/blog/management/commands/ping_baidu.py b/src/DjangoBlog/blog/management/commands/ping_baidu.py deleted file mode 100644 index 2c7fbdd..0000000 --- a/src/DjangoBlog/blog/management/commands/ping_baidu.py +++ /dev/null @@ -1,50 +0,0 @@ -from django.core.management.base import BaseCommand - -from djangoblog.spider_notify import SpiderNotify -from djangoblog.utils import get_current_site -from blog.models import Article, Tag, Category - -site = get_current_site().domain - - -class Command(BaseCommand): - help = 'notify baidu url' - - def add_arguments(self, parser): - parser.add_argument( - 'data_type', - type=str, - choices=[ - 'all', - 'article', - 'tag', - 'category'], - help='article : all article,tag : all tag,category: all category,all: All of these') - - def get_full_url(self, path): - url = "https://{site}{path}".format(site=site, path=path) - return url - - def handle(self, *args, **options): - type = options['data_type'] - self.stdout.write('start get %s' % type) - - urls = [] - if type == 'article' or type == 'all': - for article in Article.objects.filter(status='p'): - urls.append(article.get_full_url()) - if type == 'tag' or type == 'all': - for tag in Tag.objects.all(): - url = tag.get_absolute_url() - urls.append(self.get_full_url(url)) - if type == 'category' or type == 'all': - for category in Category.objects.all(): - url = category.get_absolute_url() - urls.append(self.get_full_url(url)) - - self.stdout.write( - self.style.SUCCESS( - 'start notify %d urls' % - len(urls))) - SpiderNotify.baidu_notify(urls) - self.stdout.write(self.style.SUCCESS('finish notify')) diff --git a/src/DjangoBlog/blog/management/commands/sync_user_avatar.py b/src/DjangoBlog/blog/management/commands/sync_user_avatar.py deleted file mode 100644 index d0f4612..0000000 --- a/src/DjangoBlog/blog/management/commands/sync_user_avatar.py +++ /dev/null @@ -1,47 +0,0 @@ -import requests -from django.core.management.base import BaseCommand -from django.templatetags.static import static - -from djangoblog.utils import save_user_avatar -from oauth.models import OAuthUser -from oauth.oauthmanager import get_manager_by_type - - -class Command(BaseCommand): - help = 'sync user avatar' - - def test_picture(self, url): - try: - if requests.get(url, timeout=2).status_code == 200: - return True - except: - pass - - def handle(self, *args, **options): - static_url = static("../") - users = OAuthUser.objects.all() - self.stdout.write(f'开始同步{len(users)}个用户头像') - for u in users: - self.stdout.write(f'开始同步:{u.nickname}') - url = u.picture - if url: - if url.startswith(static_url): - if self.test_picture(url): - continue - else: - if u.metadata: - manage = get_manager_by_type(u.type) - url = manage.get_picture(u.metadata) - url = save_user_avatar(url) - else: - url = static('blog/img/avatar.png') - else: - url = save_user_avatar(url) - else: - url = static('blog/img/avatar.png') - if url: - self.stdout.write( - f'结束同步:{u.nickname}.url:{url}') - u.picture = url - u.save() - self.stdout.write('结束同步') diff --git a/src/DjangoBlog/blog/middleware.py b/src/DjangoBlog/blog/middleware.py deleted file mode 100644 index 94dd70c..0000000 --- a/src/DjangoBlog/blog/middleware.py +++ /dev/null @@ -1,42 +0,0 @@ -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/DjangoBlog/blog/migrations/0001_initial.py b/src/DjangoBlog/blog/migrations/0001_initial.py deleted file mode 100644 index 3d391b6..0000000 --- a/src/DjangoBlog/blog/migrations/0001_initial.py +++ /dev/null @@ -1,137 +0,0 @@ -# Generated by Django 4.1.7 on 2023-03-02 07:14 - -from django.conf import settings -from django.db import migrations, models -import django.db.models.deletion -import django.utils.timezone -import mdeditor.fields - - -class Migration(migrations.Migration): - - initial = True - - dependencies = [ - migrations.swappable_dependency(settings.AUTH_USER_MODEL), - ] - - operations = [ - migrations.CreateModel( - name='BlogSettings', - fields=[ - ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('sitename', models.CharField(default='', max_length=200, verbose_name='网站名称')), - ('site_description', models.TextField(default='', max_length=1000, verbose_name='网站描述')), - ('site_seo_description', models.TextField(default='', max_length=1000, verbose_name='网站SEO描述')), - ('site_keywords', models.TextField(default='', max_length=1000, verbose_name='网站关键字')), - ('article_sub_length', models.IntegerField(default=300, verbose_name='文章摘要长度')), - ('sidebar_article_count', models.IntegerField(default=10, verbose_name='侧边栏文章数目')), - ('sidebar_comment_count', models.IntegerField(default=5, verbose_name='侧边栏评论数目')), - ('article_comment_count', models.IntegerField(default=5, verbose_name='文章页面默认显示评论数目')), - ('show_google_adsense', models.BooleanField(default=False, verbose_name='是否显示谷歌广告')), - ('google_adsense_codes', models.TextField(blank=True, default='', max_length=2000, null=True, verbose_name='广告内容')), - ('open_site_comment', models.BooleanField(default=True, verbose_name='是否打开网站评论功能')), - ('beiancode', models.CharField(blank=True, default='', max_length=2000, null=True, verbose_name='备案号')), - ('analyticscode', models.TextField(default='', max_length=1000, verbose_name='网站统计代码')), - ('show_gongan_code', models.BooleanField(default=False, verbose_name='是否显示公安备案号')), - ('gongan_beiancode', models.TextField(blank=True, default='', max_length=2000, null=True, verbose_name='公安备案号')), - ], - options={ - 'verbose_name': '网站配置', - 'verbose_name_plural': '网站配置', - }, - ), - migrations.CreateModel( - name='Links', - fields=[ - ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('name', models.CharField(max_length=30, unique=True, verbose_name='链接名称')), - ('link', models.URLField(verbose_name='链接地址')), - ('sequence', models.IntegerField(unique=True, verbose_name='排序')), - ('is_enable', models.BooleanField(default=True, verbose_name='是否显示')), - ('show_type', models.CharField(choices=[('i', '首页'), ('l', '列表页'), ('p', '文章页面'), ('a', '全站'), ('s', '友情链接页面')], default='i', max_length=1, verbose_name='显示类型')), - ('created_time', models.DateTimeField(default=django.utils.timezone.now, verbose_name='创建时间')), - ('last_mod_time', models.DateTimeField(default=django.utils.timezone.now, verbose_name='修改时间')), - ], - options={ - 'verbose_name': '友情链接', - 'verbose_name_plural': '友情链接', - 'ordering': ['sequence'], - }, - ), - migrations.CreateModel( - name='SideBar', - fields=[ - ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('name', models.CharField(max_length=100, verbose_name='标题')), - ('content', models.TextField(verbose_name='内容')), - ('sequence', models.IntegerField(unique=True, verbose_name='排序')), - ('is_enable', models.BooleanField(default=True, verbose_name='是否启用')), - ('created_time', models.DateTimeField(default=django.utils.timezone.now, verbose_name='创建时间')), - ('last_mod_time', models.DateTimeField(default=django.utils.timezone.now, verbose_name='修改时间')), - ], - options={ - 'verbose_name': '侧边栏', - 'verbose_name_plural': '侧边栏', - 'ordering': ['sequence'], - }, - ), - migrations.CreateModel( - name='Tag', - fields=[ - ('id', models.AutoField(primary_key=True, serialize=False)), - ('created_time', models.DateTimeField(default=django.utils.timezone.now, verbose_name='创建时间')), - ('last_mod_time', models.DateTimeField(default=django.utils.timezone.now, verbose_name='修改时间')), - ('name', models.CharField(max_length=30, unique=True, verbose_name='标签名')), - ('slug', models.SlugField(blank=True, default='no-slug', max_length=60)), - ], - options={ - 'verbose_name': '标签', - 'verbose_name_plural': '标签', - 'ordering': ['name'], - }, - ), - migrations.CreateModel( - name='Category', - fields=[ - ('id', models.AutoField(primary_key=True, serialize=False)), - ('created_time', models.DateTimeField(default=django.utils.timezone.now, verbose_name='创建时间')), - ('last_mod_time', models.DateTimeField(default=django.utils.timezone.now, verbose_name='修改时间')), - ('name', models.CharField(max_length=30, unique=True, verbose_name='分类名')), - ('slug', models.SlugField(blank=True, default='no-slug', max_length=60)), - ('index', models.IntegerField(default=0, verbose_name='权重排序-越大越靠前')), - ('parent_category', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='blog.category', verbose_name='父级分类')), - ], - options={ - 'verbose_name': '分类', - 'verbose_name_plural': '分类', - 'ordering': ['-index'], - }, - ), - migrations.CreateModel( - name='Article', - fields=[ - ('id', models.AutoField(primary_key=True, serialize=False)), - ('created_time', models.DateTimeField(default=django.utils.timezone.now, verbose_name='创建时间')), - ('last_mod_time', models.DateTimeField(default=django.utils.timezone.now, verbose_name='修改时间')), - ('title', models.CharField(max_length=200, unique=True, verbose_name='标题')), - ('body', mdeditor.fields.MDTextField(verbose_name='正文')), - ('pub_time', models.DateTimeField(default=django.utils.timezone.now, verbose_name='发布时间')), - ('status', models.CharField(choices=[('d', '草稿'), ('p', '发表')], default='p', max_length=1, verbose_name='文章状态')), - ('comment_status', models.CharField(choices=[('o', '打开'), ('c', '关闭')], default='o', max_length=1, verbose_name='评论状态')), - ('type', models.CharField(choices=[('a', '文章'), ('p', '页面')], default='a', max_length=1, verbose_name='类型')), - ('views', models.PositiveIntegerField(default=0, verbose_name='浏览量')), - ('article_order', models.IntegerField(default=0, verbose_name='排序,数字越大越靠前')), - ('show_toc', models.BooleanField(default=False, verbose_name='是否显示toc目录')), - ('author', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL, verbose_name='作者')), - ('category', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='blog.category', verbose_name='分类')), - ('tags', models.ManyToManyField(blank=True, to='blog.tag', verbose_name='标签集合')), - ], - options={ - 'verbose_name': '文章', - 'verbose_name_plural': '文章', - 'ordering': ['-article_order', '-pub_time'], - 'get_latest_by': 'id', - }, - ), - ] diff --git a/src/DjangoBlog/blog/migrations/0002_blogsettings_global_footer_and_more.py b/src/DjangoBlog/blog/migrations/0002_blogsettings_global_footer_and_more.py deleted file mode 100644 index adbaa36..0000000 --- a/src/DjangoBlog/blog/migrations/0002_blogsettings_global_footer_and_more.py +++ /dev/null @@ -1,23 +0,0 @@ -# Generated by Django 4.1.7 on 2023-03-29 06:08 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('blog', '0001_initial'), - ] - - operations = [ - migrations.AddField( - model_name='blogsettings', - name='global_footer', - field=models.TextField(blank=True, default='', null=True, verbose_name='公共尾部'), - ), - migrations.AddField( - model_name='blogsettings', - name='global_header', - field=models.TextField(blank=True, default='', null=True, verbose_name='公共头部'), - ), - ] diff --git a/src/DjangoBlog/blog/migrations/0003_blogsettings_comment_need_review.py b/src/DjangoBlog/blog/migrations/0003_blogsettings_comment_need_review.py deleted file mode 100644 index e9f5502..0000000 --- a/src/DjangoBlog/blog/migrations/0003_blogsettings_comment_need_review.py +++ /dev/null @@ -1,17 +0,0 @@ -# Generated by Django 4.2.1 on 2023-05-09 07:45 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - dependencies = [ - ('blog', '0002_blogsettings_global_footer_and_more'), - ] - - operations = [ - migrations.AddField( - model_name='blogsettings', - name='comment_need_review', - field=models.BooleanField(default=False, verbose_name='评论是否需要审核'), - ), - ] diff --git a/src/DjangoBlog/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 deleted file mode 100644 index ceb1398..0000000 --- a/src/DjangoBlog/blog/migrations/0004_rename_analyticscode_blogsettings_analytics_code_and_more.py +++ /dev/null @@ -1,27 +0,0 @@ -# Generated by Django 4.2.1 on 2023-05-09 07:51 - -from django.db import migrations - - -class Migration(migrations.Migration): - dependencies = [ - ('blog', '0003_blogsettings_comment_need_review'), - ] - - operations = [ - migrations.RenameField( - model_name='blogsettings', - old_name='analyticscode', - new_name='analytics_code', - ), - migrations.RenameField( - model_name='blogsettings', - old_name='beiancode', - new_name='beian_code', - ), - migrations.RenameField( - model_name='blogsettings', - old_name='sitename', - new_name='site_name', - ), - ] diff --git a/src/DjangoBlog/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 deleted file mode 100644 index d08e853..0000000 --- a/src/DjangoBlog/blog/migrations/0005_alter_article_options_alter_category_options_and_more.py +++ /dev/null @@ -1,300 +0,0 @@ -# Generated by Django 4.2.5 on 2023-09-06 13:13 - -from django.conf import settings -from django.db import migrations, models -import django.db.models.deletion -import django.utils.timezone -import mdeditor.fields - - -class Migration(migrations.Migration): - - dependencies = [ - migrations.swappable_dependency(settings.AUTH_USER_MODEL), - ('blog', '0004_rename_analyticscode_blogsettings_analytics_code_and_more'), - ] - - operations = [ - migrations.AlterModelOptions( - name='article', - options={'get_latest_by': 'id', 'ordering': ['-article_order', '-pub_time'], 'verbose_name': 'article', 'verbose_name_plural': 'article'}, - ), - migrations.AlterModelOptions( - name='category', - options={'ordering': ['-index'], 'verbose_name': 'category', 'verbose_name_plural': 'category'}, - ), - migrations.AlterModelOptions( - name='links', - options={'ordering': ['sequence'], 'verbose_name': 'link', 'verbose_name_plural': 'link'}, - ), - migrations.AlterModelOptions( - name='sidebar', - options={'ordering': ['sequence'], 'verbose_name': 'sidebar', 'verbose_name_plural': 'sidebar'}, - ), - migrations.AlterModelOptions( - name='tag', - options={'ordering': ['name'], 'verbose_name': 'tag', 'verbose_name_plural': 'tag'}, - ), - migrations.RemoveField( - model_name='article', - name='created_time', - ), - migrations.RemoveField( - model_name='article', - name='last_mod_time', - ), - migrations.RemoveField( - model_name='category', - name='created_time', - ), - migrations.RemoveField( - model_name='category', - name='last_mod_time', - ), - migrations.RemoveField( - model_name='links', - name='created_time', - ), - migrations.RemoveField( - model_name='sidebar', - name='created_time', - ), - migrations.RemoveField( - model_name='tag', - name='created_time', - ), - migrations.RemoveField( - model_name='tag', - name='last_mod_time', - ), - migrations.AddField( - model_name='article', - name='creation_time', - field=models.DateTimeField(default=django.utils.timezone.now, verbose_name='creation time'), - ), - migrations.AddField( - model_name='article', - name='last_modify_time', - field=models.DateTimeField(default=django.utils.timezone.now, verbose_name='modify time'), - ), - migrations.AddField( - model_name='category', - name='creation_time', - field=models.DateTimeField(default=django.utils.timezone.now, verbose_name='creation time'), - ), - migrations.AddField( - model_name='category', - name='last_modify_time', - field=models.DateTimeField(default=django.utils.timezone.now, verbose_name='modify time'), - ), - migrations.AddField( - model_name='links', - name='creation_time', - field=models.DateTimeField(default=django.utils.timezone.now, verbose_name='creation time'), - ), - migrations.AddField( - model_name='sidebar', - name='creation_time', - field=models.DateTimeField(default=django.utils.timezone.now, verbose_name='creation time'), - ), - migrations.AddField( - model_name='tag', - name='creation_time', - field=models.DateTimeField(default=django.utils.timezone.now, verbose_name='creation time'), - ), - migrations.AddField( - model_name='tag', - name='last_modify_time', - field=models.DateTimeField(default=django.utils.timezone.now, verbose_name='modify time'), - ), - migrations.AlterField( - model_name='article', - name='article_order', - field=models.IntegerField(default=0, verbose_name='order'), - ), - migrations.AlterField( - model_name='article', - name='author', - field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL, verbose_name='author'), - ), - migrations.AlterField( - model_name='article', - name='body', - field=mdeditor.fields.MDTextField(verbose_name='body'), - ), - migrations.AlterField( - model_name='article', - name='category', - field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='blog.category', verbose_name='category'), - ), - migrations.AlterField( - model_name='article', - name='comment_status', - field=models.CharField(choices=[('o', 'Open'), ('c', 'Close')], default='o', max_length=1, verbose_name='comment status'), - ), - migrations.AlterField( - model_name='article', - name='pub_time', - field=models.DateTimeField(default=django.utils.timezone.now, verbose_name='publish time'), - ), - migrations.AlterField( - model_name='article', - name='show_toc', - field=models.BooleanField(default=False, verbose_name='show toc'), - ), - migrations.AlterField( - model_name='article', - name='status', - field=models.CharField(choices=[('d', 'Draft'), ('p', 'Published')], default='p', max_length=1, verbose_name='status'), - ), - migrations.AlterField( - model_name='article', - name='tags', - field=models.ManyToManyField(blank=True, to='blog.tag', verbose_name='tag'), - ), - migrations.AlterField( - model_name='article', - name='title', - field=models.CharField(max_length=200, unique=True, verbose_name='title'), - ), - migrations.AlterField( - model_name='article', - name='type', - field=models.CharField(choices=[('a', 'Article'), ('p', 'Page')], default='a', max_length=1, verbose_name='type'), - ), - migrations.AlterField( - model_name='article', - name='views', - field=models.PositiveIntegerField(default=0, verbose_name='views'), - ), - migrations.AlterField( - model_name='blogsettings', - name='article_comment_count', - field=models.IntegerField(default=5, verbose_name='article comment count'), - ), - migrations.AlterField( - model_name='blogsettings', - name='article_sub_length', - field=models.IntegerField(default=300, verbose_name='article sub length'), - ), - migrations.AlterField( - model_name='blogsettings', - name='google_adsense_codes', - field=models.TextField(blank=True, default='', max_length=2000, null=True, verbose_name='adsense code'), - ), - migrations.AlterField( - model_name='blogsettings', - name='open_site_comment', - field=models.BooleanField(default=True, verbose_name='open site comment'), - ), - migrations.AlterField( - model_name='blogsettings', - name='show_google_adsense', - field=models.BooleanField(default=False, verbose_name='show adsense'), - ), - migrations.AlterField( - model_name='blogsettings', - name='sidebar_article_count', - field=models.IntegerField(default=10, verbose_name='sidebar article count'), - ), - migrations.AlterField( - model_name='blogsettings', - name='sidebar_comment_count', - field=models.IntegerField(default=5, verbose_name='sidebar comment count'), - ), - migrations.AlterField( - model_name='blogsettings', - name='site_description', - field=models.TextField(default='', max_length=1000, verbose_name='site description'), - ), - migrations.AlterField( - model_name='blogsettings', - name='site_keywords', - field=models.TextField(default='', max_length=1000, verbose_name='site keywords'), - ), - migrations.AlterField( - model_name='blogsettings', - name='site_name', - field=models.CharField(default='', max_length=200, verbose_name='site name'), - ), - migrations.AlterField( - model_name='blogsettings', - name='site_seo_description', - field=models.TextField(default='', max_length=1000, verbose_name='site seo description'), - ), - migrations.AlterField( - model_name='category', - name='index', - field=models.IntegerField(default=0, verbose_name='index'), - ), - migrations.AlterField( - model_name='category', - name='name', - field=models.CharField(max_length=30, unique=True, verbose_name='category name'), - ), - migrations.AlterField( - model_name='category', - name='parent_category', - field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='blog.category', verbose_name='parent category'), - ), - migrations.AlterField( - model_name='links', - name='is_enable', - field=models.BooleanField(default=True, verbose_name='is show'), - ), - migrations.AlterField( - model_name='links', - name='last_mod_time', - field=models.DateTimeField(default=django.utils.timezone.now, verbose_name='modify time'), - ), - migrations.AlterField( - model_name='links', - name='link', - field=models.URLField(verbose_name='link'), - ), - migrations.AlterField( - model_name='links', - name='name', - field=models.CharField(max_length=30, unique=True, verbose_name='link name'), - ), - migrations.AlterField( - model_name='links', - name='sequence', - field=models.IntegerField(unique=True, verbose_name='order'), - ), - migrations.AlterField( - model_name='links', - name='show_type', - field=models.CharField(choices=[('i', 'index'), ('l', 'list'), ('p', 'post'), ('a', 'all'), ('s', 'slide')], default='i', max_length=1, verbose_name='show type'), - ), - migrations.AlterField( - model_name='sidebar', - name='content', - field=models.TextField(verbose_name='content'), - ), - migrations.AlterField( - model_name='sidebar', - name='is_enable', - field=models.BooleanField(default=True, verbose_name='is enable'), - ), - migrations.AlterField( - model_name='sidebar', - name='last_mod_time', - field=models.DateTimeField(default=django.utils.timezone.now, verbose_name='modify time'), - ), - migrations.AlterField( - model_name='sidebar', - name='name', - field=models.CharField(max_length=100, verbose_name='title'), - ), - migrations.AlterField( - model_name='sidebar', - name='sequence', - field=models.IntegerField(unique=True, verbose_name='order'), - ), - migrations.AlterField( - model_name='tag', - name='name', - field=models.CharField(max_length=30, unique=True, verbose_name='tag name'), - ), - ] diff --git a/src/DjangoBlog/blog/migrations/0006_alter_blogsettings_options.py b/src/DjangoBlog/blog/migrations/0006_alter_blogsettings_options.py deleted file mode 100644 index e36feb4..0000000 --- a/src/DjangoBlog/blog/migrations/0006_alter_blogsettings_options.py +++ /dev/null @@ -1,17 +0,0 @@ -# Generated by Django 4.2.7 on 2024-01-26 02:41 - -from django.db import migrations - - -class Migration(migrations.Migration): - - dependencies = [ - ('blog', '0005_alter_article_options_alter_category_options_and_more'), - ] - - operations = [ - migrations.AlterModelOptions( - name='blogsettings', - options={'verbose_name': 'Website configuration', 'verbose_name_plural': 'Website configuration'}, - ), - ] diff --git a/src/DjangoBlog/blog/migrations/__init__.py b/src/DjangoBlog/blog/migrations/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/src/DjangoBlog/blog/models.py b/src/DjangoBlog/blog/models.py deleted file mode 100644 index 083788b..0000000 --- a/src/DjangoBlog/blog/models.py +++ /dev/null @@ -1,376 +0,0 @@ -import logging -import re -from abc import abstractmethod - -from django.conf import settings -from django.core.exceptions import ValidationError -from django.db import models -from django.urls import reverse -from django.utils.timezone import now -from django.utils.translation import gettext_lazy as _ -from mdeditor.fields import MDTextField -from uuslug import slugify - -from djangoblog.utils import cache_decorator, cache -from djangoblog.utils import get_current_site - -logger = logging.getLogger(__name__) - - -class LinkShowType(models.TextChoices): - I = ('i', _('index')) - L = ('l', _('list')) - P = ('p', _('post')) - A = ('a', _('all')) - S = ('s', _('slide')) - - -class BaseModel(models.Model): - id = models.AutoField(primary_key=True) - creation_time = models.DateTimeField(_('creation time'), default=now) - last_modify_time = models.DateTimeField(_('modify time'), default=now) - - def save(self, *args, **kwargs): - is_update_views = isinstance( - self, - Article) and 'update_fields' in kwargs and kwargs['update_fields'] == ['views'] - if is_update_views: - Article.objects.filter(pk=self.pk).update(views=self.views) - else: - if 'slug' in self.__dict__: - slug = getattr( - self, 'title') if 'title' in self.__dict__ else getattr( - self, 'name') - setattr(self, 'slug', slugify(slug)) - super().save(*args, **kwargs) - - def get_full_url(self): - site = get_current_site().domain - url = "https://{site}{path}".format(site=site, - path=self.get_absolute_url()) - return url - - class Meta: - abstract = True - - @abstractmethod - def get_absolute_url(self): - pass - - -class Article(BaseModel): - """文章""" - STATUS_CHOICES = ( - ('d', _('Draft')), - ('p', _('Published')), - ) - COMMENT_STATUS = ( - ('o', _('Open')), - ('c', _('Close')), - ) - TYPE = ( - ('a', _('Article')), - ('p', _('Page')), - ) - title = models.CharField(_('title'), max_length=200, unique=True) - body = MDTextField(_('body')) - pub_time = models.DateTimeField( - _('publish time'), blank=False, null=False, default=now) - status = models.CharField( - _('status'), - max_length=1, - choices=STATUS_CHOICES, - default='p') - comment_status = models.CharField( - _('comment status'), - max_length=1, - choices=COMMENT_STATUS, - default='o') - type = models.CharField(_('type'), max_length=1, choices=TYPE, default='a') - views = models.PositiveIntegerField(_('views'), default=0) - author = models.ForeignKey( - settings.AUTH_USER_MODEL, - verbose_name=_('author'), - blank=False, - null=False, - on_delete=models.CASCADE) - article_order = models.IntegerField( - _('order'), blank=False, null=False, default=0) - show_toc = models.BooleanField(_('show toc'), blank=False, null=False, default=False) - category = models.ForeignKey( - 'Category', - verbose_name=_('category'), - on_delete=models.CASCADE, - blank=False, - null=False) - tags = models.ManyToManyField('Tag', verbose_name=_('tag'), blank=True) - - def body_to_string(self): - return self.body - - def __str__(self): - return self.title - - class Meta: - ordering = ['-article_order', '-pub_time'] - verbose_name = _('article') - verbose_name_plural = verbose_name - get_latest_by = 'id' - - def get_absolute_url(self): - return reverse('blog:detailbyid', kwargs={ - 'article_id': self.id, - 'year': self.creation_time.year, - 'month': self.creation_time.month, - 'day': self.creation_time.day - }) - - @cache_decorator(60 * 60 * 10) - def get_category_tree(self): - tree = self.category.get_category_tree() - names = list(map(lambda c: (c.name, c.get_absolute_url()), tree)) - - return names - - def save(self, *args, **kwargs): - super().save(*args, **kwargs) - - def viewed(self): - self.views += 1 - self.save(update_fields=['views']) - - def comment_list(self): - cache_key = 'article_comments_{id}'.format(id=self.id) - value = cache.get(cache_key) - if value: - logger.info('get article comments:{id}'.format(id=self.id)) - return value - else: - comments = self.comment_set.filter(is_enable=True).order_by('-id') - cache.set(cache_key, comments, 60 * 100) - logger.info('set article comments:{id}'.format(id=self.id)) - return comments - - def get_admin_url(self): - info = (self._meta.app_label, self._meta.model_name) - return reverse('admin:%s_%s_change' % info, args=(self.pk,)) - - @cache_decorator(expiration=60 * 100) - def next_article(self): - # 下一篇 - return Article.objects.filter( - id__gt=self.id, status='p').order_by('id').first() - - @cache_decorator(expiration=60 * 100) - def prev_article(self): - # 前一篇 - return Article.objects.filter(id__lt=self.id, status='p').first() - - def get_first_image_url(self): - """ - Get the first image url from article.body. - :return: - """ - match = re.search(r'!\[.*?\]\((.+?)\)', self.body) - if match: - return match.group(1) - return "" - - -class Category(BaseModel): - """文章分类""" - name = models.CharField(_('category name'), max_length=30, unique=True) - parent_category = models.ForeignKey( - 'self', - verbose_name=_('parent category'), - blank=True, - null=True, - on_delete=models.CASCADE) - slug = models.SlugField(default='no-slug', max_length=60, blank=True) - index = models.IntegerField(default=0, verbose_name=_('index')) - - class Meta: - ordering = ['-index'] - verbose_name = _('category') - verbose_name_plural = verbose_name - - def get_absolute_url(self): - return reverse( - 'blog:category_detail', kwargs={ - 'category_name': self.slug}) - - def __str__(self): - return self.name - - @cache_decorator(60 * 60 * 10) - def get_category_tree(self): - """ - 递归获得分类目录的父级 - :return: - """ - categorys = [] - - def parse(category): - categorys.append(category) - if category.parent_category: - parse(category.parent_category) - - parse(self) - return categorys - - @cache_decorator(60 * 60 * 10) - def get_sub_categorys(self): - """ - 获得当前分类目录所有子集 - :return: - """ - categorys = [] - all_categorys = Category.objects.all() - - def parse(category): - if category not in categorys: - categorys.append(category) - childs = all_categorys.filter(parent_category=category) - for child in childs: - if category not in categorys: - categorys.append(child) - parse(child) - - parse(self) - return categorys - - -class Tag(BaseModel): - """文章标签""" - name = models.CharField(_('tag name'), max_length=30, unique=True) - slug = models.SlugField(default='no-slug', max_length=60, blank=True) - - def __str__(self): - return self.name - - def get_absolute_url(self): - return reverse('blog:tag_detail', kwargs={'tag_name': self.slug}) - - @cache_decorator(60 * 60 * 10) - def get_article_count(self): - return Article.objects.filter(tags__name=self.name).distinct().count() - - class Meta: - ordering = ['name'] - verbose_name = _('tag') - verbose_name_plural = verbose_name - - -class Links(models.Model): - """友情链接""" - - name = models.CharField(_('link name'), max_length=30, unique=True) - link = models.URLField(_('link')) - sequence = models.IntegerField(_('order'), unique=True) - is_enable = models.BooleanField( - _('is show'), default=True, blank=False, null=False) - show_type = models.CharField( - _('show type'), - max_length=1, - choices=LinkShowType.choices, - default=LinkShowType.I) - creation_time = models.DateTimeField(_('creation time'), default=now) - last_mod_time = models.DateTimeField(_('modify time'), default=now) - - class Meta: - ordering = ['sequence'] - verbose_name = _('link') - verbose_name_plural = verbose_name - - def __str__(self): - return self.name - - -class SideBar(models.Model): - """侧边栏,可以展示一些html内容""" - name = models.CharField(_('title'), max_length=100) - content = models.TextField(_('content')) - sequence = models.IntegerField(_('order'), unique=True) - is_enable = models.BooleanField(_('is enable'), default=True) - creation_time = models.DateTimeField(_('creation time'), default=now) - last_mod_time = models.DateTimeField(_('modify time'), default=now) - - class Meta: - ordering = ['sequence'] - verbose_name = _('sidebar') - verbose_name_plural = verbose_name - - def __str__(self): - return self.name - - -class BlogSettings(models.Model): - """blog的配置""" - site_name = models.CharField( - _('site name'), - max_length=200, - null=False, - blank=False, - default='') - site_description = models.TextField( - _('site description'), - max_length=1000, - null=False, - blank=False, - default='') - site_seo_description = models.TextField( - _('site seo description'), max_length=1000, null=False, blank=False, default='') - site_keywords = models.TextField( - _('site keywords'), - max_length=1000, - null=False, - blank=False, - default='') - article_sub_length = models.IntegerField(_('article sub length'), default=300) - sidebar_article_count = models.IntegerField(_('sidebar article count'), default=10) - sidebar_comment_count = models.IntegerField(_('sidebar comment count'), default=5) - article_comment_count = models.IntegerField(_('article comment count'), default=5) - show_google_adsense = models.BooleanField(_('show adsense'), default=False) - google_adsense_codes = models.TextField( - _('adsense code'), max_length=2000, null=True, blank=True, default='') - open_site_comment = models.BooleanField(_('open site comment'), default=True) - global_header = models.TextField("公共头部", null=True, blank=True, default='') - global_footer = models.TextField("公共尾部", null=True, blank=True, default='') - beian_code = models.CharField( - '备案号', - max_length=2000, - null=True, - blank=True, - default='') - analytics_code = models.TextField( - "网站统计代码", - max_length=1000, - null=False, - blank=False, - default='') - show_gongan_code = models.BooleanField( - '是否显示公安备案号', default=False, null=False) - gongan_beiancode = models.TextField( - '公安备案号', - max_length=2000, - null=True, - blank=True, - default='') - comment_need_review = models.BooleanField( - '评论是否需要审核', default=False, null=False) - - class Meta: - verbose_name = _('Website configuration') - verbose_name_plural = verbose_name - - def __str__(self): - return self.site_name - - def clean(self): - if BlogSettings.objects.exclude(id=self.id).count(): - raise ValidationError(_('There can only be one configuration')) - - def save(self, *args, **kwargs): - super().save(*args, **kwargs) - from djangoblog.utils import cache - cache.clear() diff --git a/src/DjangoBlog/blog/search_indexes.py b/src/DjangoBlog/blog/search_indexes.py deleted file mode 100644 index 7f1dfac..0000000 --- a/src/DjangoBlog/blog/search_indexes.py +++ /dev/null @@ -1,13 +0,0 @@ -from haystack import indexes - -from blog.models import Article - - -class ArticleIndex(indexes.SearchIndex, indexes.Indexable): - text = indexes.CharField(document=True, use_template=True) - - def get_model(self): - return Article - - def index_queryset(self, using=None): - return self.get_model().objects.filter(status='p') diff --git a/src/DjangoBlog/blog/static/account/css/account.css b/src/DjangoBlog/blog/static/account/css/account.css deleted file mode 100644 index 7d4cec7..0000000 --- a/src/DjangoBlog/blog/static/account/css/account.css +++ /dev/null @@ -1,9 +0,0 @@ -.button { - border: none; - padding: 4px 80px; - text-align: center; - text-decoration: none; - display: inline-block; - font-size: 16px; - margin: 4px 2px; -} \ No newline at end of file diff --git a/src/DjangoBlog/blog/static/account/js/account.js b/src/DjangoBlog/blog/static/account/js/account.js deleted file mode 100644 index f1a8771..0000000 --- a/src/DjangoBlog/blog/static/account/js/account.js +++ /dev/null @@ -1,47 +0,0 @@ -let wait = 60; - -function time(o) { - if (wait == 0) { - o.removeAttribute("disabled"); - o.value = "获取验证码"; - wait = 60 - return false - } else { - o.setAttribute("disabled", true); - o.value = "重新发送(" + wait + ")"; - wait--; - setTimeout(function () { - time(o) - }, - 1000) - } -} - -document.getElementById("btn").onclick = function () { - let id_email = $("#id_email") - let token = $("*[name='csrfmiddlewaretoken']").val() - let ts = this - let myErr = $("#myErr") - $.ajax( - { - url: "/forget_password_code/", - type: "POST", - data: { - "email": id_email.val(), - "csrfmiddlewaretoken": token - }, - success: function (result) { - if (result != "ok") { - myErr.remove() - id_email.after("") - return - } - myErr.remove() - time(ts) - }, - error: function (e) { - alert("发送失败,请重试") - } - } - ); -} diff --git a/src/DjangoBlog/blog/static/assets/css/bootstrap.min.css b/src/DjangoBlog/blog/static/assets/css/bootstrap.min.css deleted file mode 100644 index ed3905e..0000000 --- a/src/DjangoBlog/blog/static/assets/css/bootstrap.min.css +++ /dev/null @@ -1,6 +0,0 @@ -/*! - * 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(../fonts/glyphicons-halflings-regular.eot);src:url(../fonts/glyphicons-halflings-regular.eot?#iefix) format('embedded-opentype'),url(../fonts/glyphicons-halflings-regular.woff2) format('woff2'),url(../fonts/glyphicons-halflings-regular.woff) format('woff'),url(../fonts/glyphicons-halflings-regular.ttf) format('truetype'),url(../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}} -/*# sourceMappingURL=bootstrap.min.css.map */ \ No newline at end of file diff --git a/src/DjangoBlog/blog/static/assets/css/docs.min.css b/src/DjangoBlog/blog/static/assets/css/docs.min.css deleted file mode 100644 index 3945197..0000000 --- a/src/DjangoBlog/blog/static/assets/css/docs.min.css +++ /dev/null @@ -1,11 +0,0 @@ -/*! - * 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}.hll{background-color:#ffc}.c{color:#999}.err{color:#A00;background-color:#FAA}.k{color:#069}.o{color:#555}.cm{color:#999}.cp{color:#099}.c1{color:#999}.cs{color:#999}.gd{background-color:#FCC;border:1px solid #C00}.ge{font-style:italic}.gr{color:red}.gh{color:#030}.gi{background-color:#CFC;border:1px solid #0C0}.go{color:#AAA}.gp{color:#009}.gu{color:#030}.gt{color:#9C6}.kc{color:#069}.kd{color:#069}.kn{color:#069}.kp{color:#069}.kr{color:#069}.kt{color:#078}.m{color:#F60}.s{color:#d44950}.na{color:#4f9fcf}.nb{color:#366}.nc{color:#0A8}.no{color:#360}.nd{color:#99F}.ni{color:#999}.ne{color:#C00}.nf{color:#C0F}.nl{color:#99F}.nn{color:#0CF}.nt{color:#2f6f9f}.nv{color:#033}.ow{color:#000}.w{color:#bbb}.mf{color:#F60}.mh{color:#F60}.mi{color:#F60}.mo{color:#F60}.sb{color:#C30}.sc{color:#C30}.sd{color:#C30;font-style:italic}.s2{color:#C30}.se{color:#C30}.sh{color:#C30}.si{color:#A00}.sx{color:#C30}.sr{color:#3AA}.s1{color:#C30}.ss{color:#FC3}.bp{color:#366}.vc{color:#033}.vg{color:#033}.vi{color:#033}.il{color:#F60}.css .nt+.nt,.css .o,.css .o+.nt{color:#999}.select2-container{position:relative;display:inline-block;zoom:1;*display:inline;vertical-align:top;padding:0;border:0}.select2-container:hover{border:0;-webkit-box-shadow:none;-moz-box-shadow:none;box-shadow:none}.select2-container,.select2-drop,.select2-search,.select2-search input{-moz-box-sizing:border-box;-ms-box-sizing:border-box;-webkit-box-sizing:border-box;-khtml-box-sizing:border-box;box-sizing:border-box}.select2-container .select2-choice{display:block;overflow:hidden;text-decoration:none;padding:4px 12px;margin:0;color:#333;text-shadow:0 1px 0 #fff;white-space:nowrap;font-family:Arial,Helvetica,sans-serif;font-weight:700;font-size:13px;cursor:default;height:18px;background-color:#f3f3f3;background-image:-moz-linear-gradient(top,#f5f5f5,#f1f1f1);background-image:-webkit-gradient(linear,0 0,0 100%,from(#f5f5f5),to(#f1f1f1));background-image:-webkit-linear-gradient(top,#f5f5f5,#f1f1f1);background-image:-o-linear-gradient(top,#f5f5f5,#f1f1f1);background-image:linear-gradient(to bottom,#f5f5f5,#f1f1f1);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff5f5f5', endColorstr='#fff1f1f1', GradientType=0);-webkit-background-clip:padding;-moz-background-clip:padding;background-clip:padding;border:1px solid #dcdcdc;-webkit-border-radius:2px;-moz-border-radius:2px;border-radius:2px;-webkit-box-shadow:none;-moz-box-shadow:none;box-shadow:none;-moz-box-sizing:content-box;-ms-box-sizing:content-box;-webkit-box-sizing:content-box;-khtml-box-sizing:content-box;box-sizing:content-box}.select2-container .select2-choice:hover{color:#333;text-shadow:none;border-color:#c6c6c6;background-color:#f5f5f5;background-image:-moz-linear-gradient(top,#f8f8f8,#f1f1f1);background-image:-webkit-gradient(linear,0 0,0 100%,from(#f8f8f8),to(#f1f1f1));background-image:-webkit-linear-gradient(top,#f8f8f8,#f1f1f1);background-image:-o-linear-gradient(top,#f8f8f8,#f1f1f1);background-image:linear-gradient(to bottom,#f8f8f8,#f1f1f1);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff8f8f8', endColorstr='#fff1f1f1', GradientType=0);-webkit-box-shadow:0 1px 1px rgba(0,0,0,.1);-moz-box-shadow:0 1px 1px rgba(0,0,0,.1);box-shadow:0 1px 1px rgba(0,0,0,.1);background-position:0 0;-webkit-transition:none;-moz-transition:none;-o-transition:none;transition:none;z-index:2}.select2-container-active .select2-choice:hover{border:1px solid #4D90FE}.select2-container.select2-drop-above .select2-choice{background-image:-webkit-gradient(linear,left bottom,left top,color-stop(0,#eee),color-stop(.9,#fff));background-image:-webkit-linear-gradient(center bottom,#eee 0,#fff 90%);background-image:-moz-linear-gradient(center bottom,#eee 0,#fff 90%);background-image:-o-linear-gradient(bottom,#eee 0,#fff 90%);background-image:-ms-linear-gradient(top,#eee 0,#fff 90%);filter:progid:DXImageTransform.Microsoft.gradient( startColorstr='#eeeeee', endColorstr='#ffffff', GradientType=0 );background-image:linear-gradient(top,#eee 0,#fff 90%)}.select2-container .select2-choice span{margin-right:26px;display:block;overflow:hidden;white-space:nowrap;-o-text-overflow:ellipsis;-ms-text-overflow:ellipsis;text-overflow:ellipsis}.select2-container .select2-choice abbr{display:block;position:absolute;right:26px;top:8px;width:12px;height:12px;font-size:17px;line-height:16px;color:#595959;font-weight:700;cursor:pointer;text-decoration:none;border:0;outline:0}.select2-container .select2-choice abbr:hover{color:#222;cursor:pointer}.select2-drop-mask{position:absolute;left:0;top:0;z-index:9998;opacity:0}.select2-drop{background:#fff;color:#000;border:1px solid #aaa;position:absolute;top:100%;-webkit-box-shadow:0 2px 4px rgba(0,0,0,.2);-moz-box-shadow:0 2px 4px rgba(0,0,0,.2);-o-box-shadow:0 2px 4px rgba(0,0,0,.2);box-shadow:0 2px 4px rgba(0,0,0,.2);z-index:9999;width:100%;margin-top:1px}.select2-drop.select2-drop-above{margin-top:-1px;-webkit-box-shadow:0 -2px 4px rgba(0,0,0,.2);-moz-box-shadow:0 -2px 4px rgba(0,0,0,.2);-o-box-shadow:0 -2px 4px rgba(0,0,0,.2);box-shadow:0 -2px 4px rgba(0,0,0,.2)}.select2-container .select2-choice div{-webkit-border-radius:0 2px 2px 0;-moz-border-radius:0 2px 2px 0;border-radius:0 2px 2px 0;-moz-background-clip:padding;-webkit-background-clip:padding-box;background-clip:padding-box;position:absolute;right:0;top:0;display:block;height:100%;width:18px}.select2-container .select2-choice div b{background:url(/assets/img/select2.png) no-repeat -30px 2px;display:block;width:100%;height:100%}.select2-search{display:inline-block;white-space:nowrap;z-index:10000;min-height:26px;width:100%;margin:0;padding:4px 4px 0 4px}.select2-search-hidden{display:block;position:absolute;left:-10000px}.select2-search input{background:#fff url(/assets/img/select2.png) no-repeat 100% -22px;background:url(/assets/img/select2.png) no-repeat 100% -22px,-webkit-gradient(linear,left bottom,left top,color-stop(.85,#fff),color-stop(.99,#eee));background:url(/assets/img/select2.png) no-repeat 100% -22px,-webkit-linear-gradient(center bottom,#fff 85%,#eee 99%);background:url(/assets/img/select2.png) no-repeat 100% -22px,-moz-linear-gradient(center bottom,#fff 85%,#eee 99%);background:url(/assets/img/select2.png) no-repeat 100% -22px,-o-linear-gradient(bottom,#fff 85%,#eee 99%);background:url(/assets/img/select2.png) no-repeat 100% -22px,-ms-linear-gradient(top,#fff 85%,#eee 99%);background:url(/assets/img/select2.png) no-repeat 100% -22px,linear-gradient(top,#fff 85%,#eee 99%);padding:4px 20px 4px 5px;outline:0;border:1px solid #aaa;font-family:sans-serif;font-size:1em;width:100%;margin:0;height:auto!important;min-height:26px;-webkit-box-shadow:none;-moz-box-shadow:none;box-shadow:none;border-radius:0;-moz-border-radius:0;-webkit-border-radius:0}.select2-drop.select2-drop-above .select2-search input{margin-top:4px}.select2-search input.select2-active{background:#fff url(../img/spinner.gif) no-repeat 100%;background:url(../img/spinner.gif) no-repeat 100%,-webkit-gradient(linear,left bottom,left top,color-stop(.85,#fff),color-stop(.99,#eee));background:url(../img/spinner.gif) no-repeat 100%,-webkit-linear-gradient(center bottom,#fff 85%,#eee 99%);background:url(../img/spinner.gif) no-repeat 100%,-moz-linear-gradient(center bottom,#fff 85%,#eee 99%);background:url(../img/spinner.gif) no-repeat 100%,-o-linear-gradient(bottom,#fff 85%,#eee 99%);background:url(../img/spinner.gif) no-repeat 100%,-ms-linear-gradient(top,#fff 85%,#eee 99%);background:url(../img/spinner.gif) no-repeat 100%,linear-gradient(top,#fff 85%,#eee 99%)}.select2-container-active .select2-choice,.select2-container-active .select2-choices{border:1px solid #4D90FE;outline:0}.select2-dropdown-open .select2-choice,.select2-dropdown-open .select2-choice:hover{background-color:#f4f4f4;background-image:-moz-linear-gradient(top,#f6f6f6,#f1f1f1);background-image:-webkit-gradient(linear,0 0,0 100%,from(#f6f6f6),to(#f1f1f1));background-image:-webkit-linear-gradient(top,#f6f6f6,#f1f1f1);background-image:-o-linear-gradient(top,#f6f6f6,#f1f1f1);background-image:linear-gradient(to bottom,#f6f6f6,#f1f1f1);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff6f6f6', endColorstr='#fff1f1f1', GradientType=0);-webkit-box-shadow:inset 0 1px 2px rgba(0,0,0,.1);-moz-box-shadow:inset 0 1px 2px rgba(0,0,0,.1);box-shadow:inset 0 1px 2px rgba(0,0,0,.1)}.select2-dropdown-open .select2-choice div{background:0 0;border-left:none}.select2-results{margin:4px 1px 4px 0;padding:0;position:relative;overflow-x:hidden;overflow-y:auto;max-height:200px}.select2-results ul.select2-result-sub{margin:0}.select2-results ul.select2-result-sub>li .select2-result-label{padding-left:20px}.select2-results ul.select2-result-sub ul.select2-result-sub>li .select2-result-label{padding-left:40px}.select2-results ul.select2-result-sub ul.select2-result-sub ul.select2-result-sub>li .select2-result-label{padding-left:60px}.select2-results ul.select2-result-sub ul.select2-result-sub ul.select2-result-sub ul.select2-result-sub>li .select2-result-label{padding-left:80px}.select2-results ul.select2-result-sub ul.select2-result-sub ul.select2-result-sub ul.select2-result-sub ul.select2-result-sub>li .select2-result-label{padding-left:100px}.select2-results ul.select2-result-sub ul.select2-result-sub ul.select2-result-sub ul.select2-result-sub ul.select2-result-sub ul.select2-result-sub>li .select2-result-label{padding-left:110px}.select2-results ul.select2-result-sub ul.select2-result-sub ul.select2-result-sub ul.select2-result-sub ul.select2-result-sub ul.select2-result-sub ul.select2-result-sub>li .select2-result-label{padding-left:120px}.select2-results li{list-style:none;display:list-item}.select2-results li.select2-result-with-children>.select2-result-label{font-weight:700}.select2-results .select2-result-label{padding:3px 7px 4px;margin:0;cursor:pointer}.select2-results .select2-highlighted{background:#eee}.select2-results li em{background:#feffde;font-style:normal}.select2-results .select2-highlighted em{background:0 0}.select2-results .select2-no-results,.select2-results .select2-searching,.select2-results .select2-selection-limit{background:#f4f4f4;display:list-item;padding-left:4px}.select2-results .select2-disabled{display:none}.select2-more-results.select2-active{background:#f4f4f4 url(../img/spinner.gif) no-repeat 100%}.select2-more-results{background:#f4f4f4;display:list-item}.select2-container.select2-container-disabled .select2-choice{color:#b3b3b3;border-color:#d9d9d9;background-color:#e6e6e6;background-image:none;-webkit-box-shadow:none;-moz-box-shadow:none;box-shadow:none;text-shadow:none;cursor:default}.select2-container.select2-container-disabled .select2-choice div{opacity:.5;filter:alpha(opacity=50)}.select2-container-multi .select2-choices{background-color:#fff;border:1px solid #d9d9d9;border-top:1px solid silver;-webkit-border-radius:1px;-moz-border-radius:1px;border-radius:1px;margin:0;padding:0;cursor:text;overflow:hidden;height:auto!important;height:1%;position:relative}.select2-container-multi .select2-choices:hover{border:1px solid #b9b9b9;border-top:1px solid #a0a0a0;-webkit-box-shadow:inset 0 1px 2px rgba(0,0,0,.1);-moz-box-shadow:inset 0 1px 2px rgba(0,0,0,.1);box-shadow:inset 0 1px 2px rgba(0,0,0,.1)}.select2-container-multi .select2-choices{min-height:26px}.select2-container-multi.select2-container-active .select2-choices{border:1px solid #4D90FE;outline:0}.select2-container-multi .select2-choices li{float:left;list-style:none}.select2-container-multi .select2-choices .select2-search-field{white-space:nowrap;margin:0;padding:0}.select2-container-multi .select2-choices .select2-search-field input{color:#666;background:0 0!important;font-family:sans-serif;font-size:100%;height:23px;padding:5px;margin:1px 0;outline:0;border:0;-webkit-box-shadow:none;-moz-box-shadow:none;-o-box-shadow:none;box-shadow:none}.select2-container-multi .select2-choices .select2-search-field input.select2-active{background:#fff url(../img/spinner.gif) no-repeat 100%!important}.select2-default{color:#999!important}.select2-container-multi .select2-choices .select2-search-choice{-webkit-border-radius:2px;-moz-border-radius:2px;border-radius:2px;-moz-background-clip:padding;-webkit-background-clip:padding-box;background-clip:padding-box;background-color:#DAE4F6;color:#222;font-family:Arial;border:1px solid #DAE4F6;line-height:23px;padding:0 19px 0 5px;margin:1px;position:relative;cursor:default}.select2-container-multi .select2-choices .select2-search-choice span{cursor:default}.select2-container-multi .select2-choices .select2-search-choice-focus{background:#A6D7F5}.select2-search-choice-close{display:block;position:absolute;right:3px;top:4px;width:12px;height:13px;font-size:17px;line-height:16px;color:#444;font-weight:700;outline:0}.select2-search-choice-close:hover{text-decoration:none;color:#222;cursor:pointer}.select2-container-multi.select2-container-disabled .select2-choices{background-color:#f4f4f4;background-image:none;border:1px solid #ddd;cursor:default}.select2-container-multi.select2-container-disabled .select2-choices .select2-search-choice{background-image:none;background-color:#f4f4f4;border:1px solid #ddd;padding:3px 5px 3px 5px}.select2-container-multi.select2-container-disabled .select2-choices .select2-search-choice .select2-search-choice-close{display:none}.select2-result-selectable .select2-match,.select2-result-unselectable .select2-result-selectable .select2-match{font-weight:700}.select2-result-unselectable .select2-match{text-decoration:none}.select2-offscreen{position:absolute;left:-10000px}.select2-results::-webkit-scrollbar{height:16px;width:10px}.select2-results::-webkit-scrollbar-button:end:increment,.select2-results::-webkit-scrollbar-button:start:decrement{background-color:transparent;display:block;height:0}.select2-results::-webkit-scrollbar-track{background-clip:padding-box;border:solid transparent;border-width:0 0 0 4px}.select2-results::-webkit-scrollbar-track-piece{background-color:transparent;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0}.select2-results::-webkit-scrollbar:hover{background-color:#f3f3f3;border:1px solid #dbdbdb}.select2-results::-webkit-scrollbar-thumb:horizontal,.select2-results::-webkit-scrollbar-thumb:vertical{background-color:#c6c6c6;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0}.select2-results::-webkit-scrollbar-thumb{background-color:rgba(0,0,0,.2);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);-moz-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);background-clip:padding-box}.select2-results::-webkit-scrollbar-thumb:hover{background-color:#949494}.select2-results::-webkit-scrollbar-thumb:active{background-color:rgba(0,0,0,.5);-webkit-box-shadow:inset 1px 1px 3px rgba(0,0,0,.35);-moz-box-shadow:inset 1px 1px 3px rgba(0,0,0,.35);box-shadow:inset 1px 1px 3px rgba(0,0,0,.35)}@media only screen and (-webkit-min-device-pixel-ratio:1.5){.select2-container .select2-choice div b,.select2-search input{background-image:url(/assets/img/select2x2.png)!important;background-repeat:no-repeat!important;background-size:60px 40px!important}.select2-search input{background-position:100% -21px!important}}/*! - * Bootstrap Docs (http://getbootstrap.com) - * Copyright 2011-2016 Twitter, Inc. - * Licensed under the Creative Commons Attribution 3.0 Unported License. For - * details, see https://creativecommons.org/licenses/by/3.0/. - */body{position:relative;padding-top:94px}.table code{font-size:13px;font-weight:400}h2 code,h3 code,h4 code{background-color:inherit}.btn-outline{color:#4d90fe;background-color:transparent;border-color:#4d90fe}.btn-outline:active,.btn-outline:focus,.btn-outline:hover{color:#fff;background-color:#4d90fe;border-color:#4d90fe}.btn-outline-inverse{color:#fff;background-color:transparent;border-color:#fff}.btn-outline-inverse:active,.btn-outline-inverse:focus,.btn-outline-inverse:hover{color:#2d87e2;text-shadow:none;background-color:#fff;border-color:#fff}#skippy{display:block;padding:1em;color:#777;background-color:#f1f1f1;outline:0}#skippy .skiplink-text{padding:.5em;outline:1px dotted}#content:focus{outline:0}.bs-docs-footer{padding-top:40px;padding-bottom:30px;margin-top:100px;color:#777;text-align:center;border-top:1px solid #e5e5e5}.bs-docs-footer-links{padding-left:0;margin-bottom:20px}.bs-docs-footer-links li{display:inline-block}.bs-docs-footer-links li+li{margin-left:15px}@media (min-width:768px){.bs-docs-footer{text-align:left}.bs-docs-footer p{margin-bottom:0}}.bs-docs-header,.bs-docs-masthead{position:relative;padding:30px 0;color:#b3d4f4;text-align:center;text-shadow:0 1px 0 rgba(0,0,0,.1);background-color:#2d87e2;background-image:-webkit-linear-gradient(top,#1b6ec1 0,#2d87e2 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#1b6ec1),to(#2d87e2));background-image:-o-linear-gradient(top,#1b6ec1 0,#2d87e2 100%);background-image:linear-gradient(to bottom,#1b6ec1 0,#2d87e2 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#1b6ec1', endColorstr='#2d87e2', GradientType=0);background-repeat:repeat-x}.bs-docs-masthead .bs-docs-booticon{margin:0 auto 30px}.bs-docs-masthead h1{font-weight:300;line-height:1;color:#fff}.bs-docs-masthead .lead{margin:0 auto 30px;font-size:20px;color:#fff}.bs-docs-masthead .version{margin-top:-15px;color:#b3d4f4}.bs-docs-masthead .btn{width:100%;padding:15px 30px;font-size:20px}@media (min-width:480px){.bs-docs-masthead .btn{width:auto}}@media (min-width:768px){.bs-docs-masthead{padding:80px 0}.bs-docs-masthead h1{font-size:60px}.bs-docs-masthead .lead{font-size:24px}}@media (min-width:992px){.bs-docs-masthead .lead{width:80%;font-size:30px}}.bs-docs-header{margin-bottom:40px;font-size:20px}.bs-docs-header h1{margin-top:0;color:#fff}.bs-docs-header p{margin-bottom:0;font-weight:300;line-height:1.4}.bs-docs-header .container{position:relative}@media (min-width:768px){.bs-docs-header{padding-top:60px;padding-bottom:60px;font-size:24px;text-align:left}.bs-docs-header h1{font-size:60px;line-height:1}}@media (min-width:992px){.bs-docs-header h1,.bs-docs-header p{margin-right:380px}}.bs-docs-featurette{padding-top:40px;padding-bottom:40px;font-size:16px;line-height:1.5;color:#555;text-align:center;background-color:#fff;border-bottom:1px solid #e5e5e5}.bs-docs-featurette+.bs-docs-footer{margin-top:0;border-top:0}.bs-docs-featurette-title{margin-bottom:5px;font-size:30px;font-weight:400;color:#333}.half-rule{width:100px;margin:40px auto}.bs-docs-featurette h3{margin-bottom:5px;font-weight:400;color:#333}.bs-docs-featurette-img{display:block;margin-bottom:20px;color:#333}.bs-docs-featurette-img:hover{color:#337ab7;text-decoration:none}.bs-docs-featurette-img img{display:block;margin-bottom:15px}@media (min-width:480px){.bs-docs-featurette .img-responsive{margin-top:30px}}@media (min-width:768px){.bs-docs-featurette{padding-top:100px;padding-bottom:100px}.bs-docs-featurette-title{font-size:40px}.bs-docs-featurette .lead{max-width:80%;margin-right:auto;margin-left:auto}.bs-docs-featurette .img-responsive{margin-top:0}}.bs-docs-featured-sites{margin-right:-1px;margin-left:-1px}.bs-docs-featured-sites .col-xs-6{padding:1px}.bs-docs-featured-sites .img-responsive{margin-top:0}@media (min-width:768px){.bs-docs-featured-sites .col-sm-3:first-child img{border-top-left-radius:4px;border-bottom-left-radius:4px}.bs-docs-featured-sites .col-sm-3:last-child img{border-top-right-radius:4px;border-bottom-right-radius:4px}}.bs-examples .thumbnail{margin-bottom:10px}.bs-examples h4{margin-bottom:5px}.bs-examples p{margin-bottom:20px}@media (max-width:480px){.bs-examples{margin-right:-10px;margin-left:-10px}.bs-examples>[class^=col-]{padding-right:10px;padding-left:10px}}.bs-docs-sidebar.affix{position:static}@media (min-width:768px){.bs-docs-sidebar{padding-left:20px}}.bs-docs-sidenav{margin-top:50px;margin-bottom:20px}.bs-docs-sidebar .nav>li>a{display:block;padding:5px 20px;font-size:13px;font-weight:500;color:#222}.bs-docs-sidebar .nav>li>a:focus,.bs-docs-sidebar .nav>li>a:hover{text-decoration:none;background-color:#eee}.bs-docs-sidebar .nav>.active:focus>a,.bs-docs-sidebar .nav>.active:hover>a,.bs-docs-sidebar .nav>.active>a{color:#dd4b39;background-color:transparent}.bs-docs-sidebar .nav .nav{display:none;margin-bottom:8px}.bs-docs-sidebar .nav .nav>li>a{padding-top:1px;padding-bottom:1px;padding-left:30px;font-size:12px}.back-to-top,.bs-docs-theme-toggle{display:none;padding:4px 10px;margin-top:10px;margin-left:10px;font-size:12px;font-weight:500;color:#999}.back-to-top:hover,.bs-docs-theme-toggle:hover{color:#563d7c;text-decoration:none}.bs-docs-theme-toggle{margin-top:0}@media (min-width:768px){.back-to-top,.bs-docs-theme-toggle{display:block}}@media (min-width:992px){.bs-docs-sidebar .nav>.active>ul{display:block}.bs-docs-sidebar.affix,.bs-docs-sidebar.affix-bottom{width:213px}.bs-docs-sidebar.affix{position:fixed;top:80px}.bs-docs-sidebar.affix-bottom{position:absolute}.bs-docs-sidebar.affix .bs-docs-sidenav,.bs-docs-sidebar.affix-bottom .bs-docs-sidenav{margin-top:0;margin-bottom:0}}@media (min-width:1200px){.bs-docs-sidebar.affix,.bs-docs-sidebar.affix-bottom{width:263px}}.bs-docs-section{margin-bottom:60px}.bs-docs-section:last-child{margin-bottom:0}h1[id]{padding-top:20px;margin-top:0}.bs-callout{padding:20px;margin:20px 0;border:1px solid #eee;border-left-width:5px;border-radius:3px}.bs-callout h4{margin-top:0;margin-bottom:5px}.bs-callout p:last-child{margin-bottom:0}.bs-callout code{border-radius:3px}.bs-callout+.bs-callout{margin-top:-5px}.bs-callout-danger{border-left-color:#dd4b39}.bs-callout-danger h4{color:#c23321}.bs-callout-warning{border-left-color:#f1e7bc}.bs-callout-warning h4{color:#ba9e27}.bs-callout-info{border-left-color:#d0e3f0}.bs-callout-info h4{color:#3b86b9}.color-swatches{margin:0 -5px;overflow:hidden}.color-swatch{float:left;width:60px;height:60px;margin:0 5px;border-radius:3px}@media (min-width:768px){.color-swatch{width:100px;height:100px}}.color-swatches .gray-darker{background-color:#222}.color-swatches .gray-dark{background-color:#333}.color-swatches .gray{background-color:#555}.color-swatches .gray-light{background-color:#999}.color-swatches .gray-lighter{background-color:#eee}.color-swatches .brand-primary{background-color:#4d90fe}.color-swatches .brand-success{background-color:#35aa47}.color-swatches .brand-warning{background-color:#faa937}.color-swatches .brand-danger{background-color:#d84a38}.color-swatches .brand-info{background-color:#5bc0de}.color-swatches .bs-purple{background-color:#1b6ec1}.color-swatches .bs-purple-light{background-color:#c7bfd3}.color-swatches .bs-purple-lighter{background-color:#e5e1ea}.color-swatches .bs-gray{background-color:#f9f9f9}.bs-team .team-member{line-height:32px;color:#555}.bs-team .team-member:hover{color:#333;text-decoration:none}.bs-team .github-btn{float:right;width:180px;height:20px;margin-top:6px;border:none}.bs-team img{float:left;width:32px;margin-right:10px;border-radius:4px}.bs-docs-browser-bugs td p{margin-bottom:0}.bs-docs-browser-bugs th:first-child{width:18%}.show-grid{margin-bottom:15px}.show-grid [class^=col-]{padding-top:10px;padding-bottom:10px;background-color:#f9f9f9;border:1px solid #ddd}.bs-example{position:relative;padding:45px 15px 15px;margin:0 -15px 15px;border-color:#e5e5e5 #eee #eee;border-style:solid;border-width:1px 0;-webkit-box-shadow:inset 0 3px 6px rgba(0,0,0,.05);box-shadow:inset 0 3px 6px rgba(0,0,0,.05)}.bs-example:after{position:absolute;top:15px;left:15px;font-size:12px;font-weight:700;color:#959595;text-transform:uppercase;letter-spacing:1px;content:"Example"}.bs-example-padded-bottom{padding-bottom:24px}.bs-example+.highlight,.bs-example+.zero-clipboard+.highlight{margin:-15px -15px 15px;border-width:0 0 1px;border-radius:0}@media (min-width:768px){.bs-example{margin-right:0;margin-left:0;background-color:#fff;border-color:#ddd;border-width:1px;border-radius:4px 4px 0 0;-webkit-box-shadow:none;box-shadow:none}.bs-example+.highlight,.bs-example+.zero-clipboard+.highlight{margin-top:-16px;margin-right:0;margin-left:0;border-width:1px;border-bottom-right-radius:4px;border-bottom-left-radius:4px}.bs-example-standalone{border-radius:4px}}.bs-example .container{width:auto}.bs-example>.alert:last-child,.bs-example>.form-control:last-child,.bs-example>.jumbotron:last-child,.bs-example>.list-group:last-child,.bs-example>.navbar:last-child,.bs-example>.panel:last-child,.bs-example>.progress:last-child,.bs-example>.table-responsive:last-child>.table,.bs-example>.table:last-child,.bs-example>.well:last-child,.bs-example>blockquote:last-child,.bs-example>ol:last-child,.bs-example>p:last-child,.bs-example>ul:last-child{margin-bottom:0}.bs-example>p>.close{float:none}.bs-example-type .table .type-info{color:#999;vertical-align:middle}.bs-example-type .table td{padding:15px 0;border-color:#eee}.bs-example-type .table tr:first-child td{border-top:0}.bs-example-type h1,.bs-example-type h2,.bs-example-type h3,.bs-example-type h4,.bs-example-type h5,.bs-example-type h6{margin:0}.bs-example-bg-classes p{padding:15px}.bs-example>.img-circle,.bs-example>.img-rounded,.bs-example>.img-thumbnail{margin:5px}.bs-example>.table-responsive>.table{background-color:#fff}.bs-example>.btn,.bs-example>.btn-group{margin-top:5px;margin-bottom:5px}.bs-example>.btn-toolbar+.btn-toolbar{margin-top:10px}.bs-example .select2-container.form-control,.bs-example-control-sizing input[type=text]+input[type=text],.bs-example-control-sizing select{margin-top:10px}.bs-example-form .input-group{margin-bottom:10px}.bs-example>textarea.form-control{resize:vertical}.bs-example>.list-group{max-width:400px}.bs-example .navbar:last-child{margin-bottom:0}.bs-navbar-bottom-example,.bs-navbar-top-example{z-index:1;padding:0;overflow:hidden}.bs-navbar-bottom-example .navbar-header,.bs-navbar-top-example .navbar-header{margin-left:0}.bs-navbar-bottom-example .navbar-fixed-bottom,.bs-navbar-top-example .navbar-fixed-top{position:relative;margin-right:0;margin-left:0}.bs-navbar-top-example{padding-bottom:90px}.bs-navbar-top-example:after{top:auto;bottom:15px}.bs-navbar-top-example .navbar-fixed-top{top:-1px}.bs-navbar-bottom-example{padding-top:90px}.bs-navbar-bottom-example .navbar-fixed-bottom{bottom:-1px}.bs-navbar-bottom-example .navbar{margin-bottom:0}@media (min-width:768px){.bs-navbar-bottom-example .navbar-fixed-bottom,.bs-navbar-top-example .navbar-fixed-top{position:absolute}}.bs-example .pagination{margin-top:10px;margin-bottom:10px}.bs-example>.pager{margin-top:0}.bs-example>.scrollable{height:200px;overflow-y:auto}.bs-example-modal{background-color:#f5f5f5}.bs-example-modal .modal{position:relative;top:auto;right:auto;bottom:auto;left:auto;z-index:1;display:block}.bs-example-modal .modal-dialog{left:auto;margin-right:auto;margin-left:auto}.bs-example .dropup>.dropdown-toggle,.bs-example>.dropdown>.dropdown-toggle{float:left}.bs-example-submenu .dropdown>.dropdown-menu,.bs-example-submenu .dropup>.dropdown-menu,.bs-example>.dropdown>.dropdown-menu{position:static;display:block;margin-bottom:5px;clear:left}.bs-example-submenu .dropdown-menu{margin-right:20px}.bs-example-tabs .nav-tabs{margin-bottom:15px}.bs-example-tooltips{text-align:center}.bs-example-tooltips>.btn{margin-top:5px;margin-bottom:5px}.bs-example-tooltip .tooltip{position:relative;display:inline-block;margin:10px 20px;opacity:1}.bs-example-popover{padding-bottom:24px;background-color:#f9f9f9}.bs-example-popover .popover{position:relative;display:block;float:left;width:260px;margin:20px}.scrollspy-example{position:relative;height:200px;margin-top:10px;overflow:auto}.bs-example>.nav-pills-stacked-example{max-width:300px}#collapseExample .well{margin-bottom:0}.bs-events-table>tbody>tr>td:first-child,.bs-events-table>thead>tr>th:first-child{white-space:nowrap}.bs-events-table>thead>tr>th:first-child{width:150px}.js-options-table>thead>tr>th:nth-child(1),.js-options-table>thead>tr>th:nth-child(2){width:100px}.js-options-table>thead>tr>th:nth-child(3){width:50px}.highlight{padding:9px 14px;margin-bottom:14px;background-color:#f7f7f9;border:1px solid #e1e1e8;border-radius:4px}.highlight pre{padding:0;margin-top:0;margin-bottom:0;word-break:normal;white-space:nowrap;background-color:transparent;border:0}.highlight pre code{font-size:inherit;color:#333}.highlight pre code:first-child{display:inline-block;padding-right:45px}.table-responsive .highlight pre{white-space:normal}.bs-table th small,.responsive-utilities th small{display:block;font-weight:400;color:#999}.responsive-utilities tbody th{font-weight:400}.responsive-utilities td{text-align:center}.responsive-utilities td.is-visible{color:#468847;background-color:#dff0d8!important}.responsive-utilities td.is-hidden{color:#ccc;background-color:#f9f9f9!important}.responsive-utilities-test{margin-top:5px}.responsive-utilities-test .col-xs-6{margin-bottom:10px}.responsive-utilities-test span{display:block;padding:15px 10px;font-size:14px;font-weight:700;line-height:1.1;text-align:center;border-radius:4px}.hidden-on .col-xs-6 .hidden-lg,.hidden-on .col-xs-6 .hidden-md,.hidden-on .col-xs-6 .hidden-sm,.hidden-on .col-xs-6 .hidden-xs,.visible-on .col-xs-6 .hidden-lg,.visible-on .col-xs-6 .hidden-md,.visible-on .col-xs-6 .hidden-sm,.visible-on .col-xs-6 .hidden-xs{color:#999;border:1px solid #ddd}.hidden-on .col-xs-6 .visible-lg-block,.hidden-on .col-xs-6 .visible-md-block,.hidden-on .col-xs-6 .visible-sm-block,.hidden-on .col-xs-6 .visible-xs-block,.visible-on .col-xs-6 .visible-lg-block,.visible-on .col-xs-6 .visible-md-block,.visible-on .col-xs-6 .visible-sm-block,.visible-on .col-xs-6 .visible-xs-block{color:#468847;background-color:#dff0d8;border:1px solid #d6e9c6}.bs-glyphicons{margin:0 -10px 20px;overflow:hidden}.bs-glyphicons-list{padding-left:0;list-style:none}.bs-glyphicons li{float:left;width:25%;height:115px;padding:10px;margin:0 -1px -1px 0;font-size:10px;line-height:1.4;text-align:center;border:1px solid #ddd}.bs-glyphicons .glyphicon{margin-top:5px;margin-bottom:10px;font-size:24px}.bs-glyphicons .glyphicon-class{display:block;text-align:center;word-wrap:break-word}.bs-glyphicons li:hover{background-color:#eee}@media (min-width:768px){.bs-glyphicons{margin-right:0;margin-left:0}.bs-glyphicons li{width:12.5%;font-size:12px}}.bs-customizer .toggle{float:right;margin-top:25px}.bs-customizer label{margin-top:10px;font-weight:500;color:#555}.bs-customizer h2{padding-top:30px;margin-top:0;margin-bottom:5px}.bs-customizer h3{margin-bottom:0}.bs-customizer h4{margin-top:15px;margin-bottom:0}.bs-customizer .bs-callout h4{margin-top:0;margin-bottom:5px}.bs-customizer input[type=text]{font-family:Menlo,Monaco,Consolas,"Courier New",monospace;background-color:#fafafa}.bs-customizer .help-block{margin-bottom:5px;font-size:12px}#less-section label{font-weight:400}.bs-customize-download .btn-outline{padding:20px}.bs-customizer-alert{position:fixed;top:0;right:0;left:0;z-index:1030;padding:15px 0;color:#fff;background-color:#d9534f;border-bottom:1px solid #b94441;-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,.25);box-shadow:inset 0 1px 0 rgba(255,255,255,.25)}.bs-customizer-alert .close{margin-top:-4px;font-size:24px}.bs-customizer-alert p{margin-bottom:0}.bs-customizer-alert .glyphicon{margin-right:5px}.bs-customizer-alert pre{margin:10px 0 0;color:#fff;background-color:#a83c3a;border-color:#973634;-webkit-box-shadow:inset 0 2px 4px rgba(0,0,0,.05),0 1px 0 rgba(255,255,255,.1);box-shadow:inset 0 2px 4px rgba(0,0,0,.05),0 1px 0 rgba(255,255,255,.1)}.bs-dropzone{position:relative;padding:20px;margin-bottom:20px;color:#777;text-align:center;border:2px dashed #eee;border-radius:4px}.bs-dropzone .import-header{margin-bottom:5px}.bs-dropzone .glyphicon-download-alt{font-size:40px}.bs-dropzone hr{width:100px}.bs-dropzone .lead{margin-bottom:10px;font-weight:400;color:#333}#import-manual-trigger{cursor:pointer}.bs-dropzone p:last-child{margin-bottom:0}.bs-brand-logos{display:table;width:100%;margin-bottom:15px;overflow:hidden;color:#1b6ec1;background-color:#f9f9f9;border-radius:4px}.bs-brand-item{padding:60px 0;text-align:center}.bs-brand-item+.bs-brand-item{border-top:1px solid #fff}.bs-brand-logos .inverse{color:#fff;background-color:#1b6ec1}.bs-brand-item h1,.bs-brand-item h3{margin-top:0;margin-bottom:0}.bs-brand-item .bs-docs-booticon{margin-right:auto;margin-left:auto}.bs-brand-item .glyphicon{width:30px;height:30px;margin:10px auto -10px;line-height:30px;color:#fff;border-radius:50%}.bs-brand-item .glyphicon-ok{background-color:#5cb85c}.bs-brand-item .glyphicon-remove{background-color:#d9534f}@media (min-width:768px){.bs-brand-item{display:table-cell;width:1%}.bs-brand-item+.bs-brand-item{border-top:0;border-left:1px solid #fff}.bs-brand-item h1{font-size:60px}}.zero-clipboard{position:relative;display:none}.btn-clipboard{position:absolute;top:0;right:0;z-index:10;display:block;padding:5px 8px;font-size:12px;color:#777;cursor:pointer;background-color:#fff;border:1px solid #e1e1e8;border-radius:0 4px 0 4px}.btn-clipboard-hover{color:#fff;background-color:#563d7c;border-color:#563d7c}@media (min-width:768px){.zero-clipboard{display:block}.bs-example+.zero-clipboard .btn-clipboard{top:-16px;border-top-right-radius:0}}.anchorjs-link{color:inherit}@media (max-width:480px){.anchorjs-link{display:none}}:hover>.anchorjs-link{opacity:.75;-webkit-transition:color .16s linear;-o-transition:color .16s linear;transition:color .16s linear}.anchorjs-link:focus,:hover>.anchorjs-link:hover{text-decoration:none;opacity:1}#focusedInput{border:1px solid #4d90fe!important;outline:0;outline:thin dotted\9;-webkit-box-shadow:none;box-shadow:none}.v4-tease{position:fixed;top:0;right:0;left:0;z-index:1030;display:block;padding:15px 20px;font-weight:700;color:#fff;text-align:center;background-color:#1b6ec1}.v4-tease:hover{color:#fff;text-decoration:none;background-color:#2d87e2}@media print{a[href]:after{content:""!important}}.bs-docs-navbar-masthead{top:48px}.bs-docs-dl-options h4{margin-top:15px;margin-bottom:5px} -/*# sourceMappingURL=docs.min.css.map */ \ No newline at end of file diff --git a/src/DjangoBlog/blog/static/assets/css/ie10-viewport-bug-workaround.css b/src/DjangoBlog/blog/static/assets/css/ie10-viewport-bug-workaround.css deleted file mode 100644 index 4b9518e..0000000 --- a/src/DjangoBlog/blog/static/assets/css/ie10-viewport-bug-workaround.css +++ /dev/null @@ -1,13 +0,0 @@ -/*! - * 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) - */ - -/* - * See the Getting Started docs for more information: - * http://getbootstrap.com/getting-started/#support-ie10-width - */ -@-ms-viewport { width: device-width; } -@-o-viewport { width: device-width; } -@viewport { width: device-width; } diff --git a/src/DjangoBlog/blog/static/assets/css/signin.css b/src/DjangoBlog/blog/static/assets/css/signin.css deleted file mode 100644 index 121fb0d..0000000 --- a/src/DjangoBlog/blog/static/assets/css/signin.css +++ /dev/null @@ -1,58 +0,0 @@ -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; -} diff --git a/src/DjangoBlog/blog/static/assets/css/todc-bootstrap.min.css b/src/DjangoBlog/blog/static/assets/css/todc-bootstrap.min.css deleted file mode 100644 index 66c9cb2..0000000 --- a/src/DjangoBlog/blog/static/assets/css/todc-bootstrap.min.css +++ /dev/null @@ -1,6 +0,0 @@ -/*! - * 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(../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)} -/*# sourceMappingURL=todc-bootstrap.min.css.map */ \ No newline at end of file diff --git a/src/DjangoBlog/blog/static/assets/img/checkmark.png b/src/DjangoBlog/blog/static/assets/img/checkmark.png deleted file mode 100644 index 4bd0eb3..0000000 Binary files a/src/DjangoBlog/blog/static/assets/img/checkmark.png and /dev/null differ diff --git a/src/DjangoBlog/blog/static/assets/js/ie-emulation-modes-warning.js b/src/DjangoBlog/blog/static/assets/js/ie-emulation-modes-warning.js deleted file mode 100644 index 3f97ba5..0000000 --- a/src/DjangoBlog/blog/static/assets/js/ie-emulation-modes-warning.js +++ /dev/null @@ -1,51 +0,0 @@ -// NOTICE!! DO NOT USE ANY OF THIS JAVASCRIPT -// IT'S JUST JUNK FOR OUR DOCS! -// ++++++++++++++++++++++++++++++++++++++++++ -/*! - * 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/. - */ -// Intended to prevent false-positive bug reports about Bootstrap not working properly in old versions of IE due to folks testing using IE's unreliable emulation modes. -(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() { - // Detects the actual version of IE in use, even if it's in an older-IE emulation mode. - // IE JavaScript conditional compilation docs: https://msdn.microsoft.com/library/121hztk3%28v=vs.94%29.aspx - // @cc_on docs: https://msdn.microsoft.com/library/8ka90k2e%28v=vs.94%29.aspx - var jscriptVersion = new Function('/*@cc_on return @_jscript_version; @*/')() // jshint ignore:line - if (jscriptVersion === undefined) { - return 11 // IE11+ not in emulation mode - } - if (jscriptVersion < 9) { - return 8 // IE8 (or lower; haven't tested on IE<8) - } - return jscriptVersion // IE9 or IE10 in any mode, or IE11 in non-IE11 mode - } - - var ua = window.navigator.userAgent - if (ua.indexOf('Opera') > -1 || ua.indexOf('Presto') > -1) { - return // Opera, which might pretend to be IE - } - var emulated = emulatedIEMajorVersion() - if (emulated === null) { - return // Not IE - } - 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!') - } -})(); diff --git a/src/DjangoBlog/blog/static/assets/js/ie10-viewport-bug-workaround.js b/src/DjangoBlog/blog/static/assets/js/ie10-viewport-bug-workaround.js deleted file mode 100644 index 479a6eb..0000000 --- a/src/DjangoBlog/blog/static/assets/js/ie10-viewport-bug-workaround.js +++ /dev/null @@ -1,23 +0,0 @@ -/*! - * 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) - */ - -// See the Getting Started docs for more information: -// http://getbootstrap.com/getting-started/#support-ie10-width - -(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) - } - -})(); diff --git a/src/DjangoBlog/blog/static/blog/css/ie.css b/src/DjangoBlog/blog/static/blog/css/ie.css deleted file mode 100644 index 706f510..0000000 --- a/src/DjangoBlog/blog/static/blog/css/ie.css +++ /dev/null @@ -1,273 +0,0 @@ -/* -Styles for older IE versions (previous to IE9). -*/ - -body { - background-color: #e6e6e6; -} -body.custom-background-empty { - background-color: #fff; -} -body.custom-background-empty .site, -body.custom-background-white .site { - box-shadow: none; - margin-bottom: 0; - margin-top: 0; - padding: 0; -} -.assistive-text, -.site .screen-reader-text { - clip: rect(1px 1px 1px 1px); -} -.full-width .site-content { - float: none; - width: 100%; -} -img.size-full, -img.size-large, -img.header-image, -img.wp-post-image, -img[class*="align"], -img[class*="wp-image-"], -img[class*="attachment-"] { - width: auto; /* Prevent stretching of full-size and large-size images with height and width attributes in IE8 */ -} -.author-avatar { - float: left; - margin-top: 8px; - margin-top: 0.571428571rem; -} -.author-description { - float: right; - width: 80%; -} -.site { - box-shadow: 0 2px 6px rgba(100, 100, 100, 0.3); - margin: 48px auto; - max-width: 960px; - overflow: hidden; - padding: 0 40px; -} -.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; - line-height: 1.846153846; -} -.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; -} -.ie7 .main-navigation li a, -.ie7 .main-navigation li { - display: inline; -} -.main-navigation li a { - border-bottom: 0; - color: #6a6a6a; - line-height: 3.692307692; - text-transform: uppercase; -} -.main-navigation li a:hover { - color: #000; -} -.main-navigation li { - margin: 0 40px 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); -} -.ie7 .main-navigation li ul { - clip: inherit; - display: none; - left: 0; - overflow: visible; -} -.main-navigation li ul ul, -.ie7 .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; -} -.ie7 .main-navigation ul li:hover > ul, -.ie7 .main-navigation ul li:focus > ul { - display: block; -} -.main-navigation li ul li a { - background: #efefef; - border-bottom: 1px solid #ededed; - display: block; - font-size: 11px; - line-height: 2.181818182; - padding: 8px 10px; - width: 180px; -} -.main-navigation li ul li a:hover { - 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; -} -.main-navigation .menu-toggle { - display: none; -} -.entry-header .entry-title { - font-size: 22px; -} -#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%; -} -/* IE Front Page Template Widget fix */ -.template-front-page .widget-area { - clear: both; -} -.template-front-page .widget { - width: 100% !important; - border: none; -} -.template-front-page .widget-area .widget, -.template-front-page .first.front-widgets, -.template-front-page.two-sidebars .widget-area .front-widgets { - float: left; - margin-bottom: 24px; - width: 51.875%; -} -.template-front-page .second.front-widgets, -.template-front-page .widget-area .widget:nth-child(odd) { - clear: right; -} -.template-front-page .first.front-widgets, -.template-front-page .second.front-widgets, -.template-front-page.two-sidebars .widget-area .front-widgets + .front-widgets { - float: right; - margin: 0 0 24px; - width: 39.0625%; -} -.template-front-page.two-sidebars .widget, -.template-front-page.two-sidebars .widget:nth-child(even) { - float: none; - width: auto; -} -/* add input font for ul { - text-align: right; -} -.rtl .main-navigation ul li ul li, -.rtl .main-navigation ul li ul li ul li { - margin-left: 40px; - margin-right: auto; -} -.rtl .main-navigation li ul ul { - position: absolute; - bottom: 0; - right: 100%; - z-index: 1; -} -.ie7 .rtl .main-navigation li ul ul { - position: absolute; - bottom: 0; - right: 100%; - z-index: 1; -} -.ie7 .rtl .main-navigation ul li { - z-index: 99; -} -.ie7 .rtl .main-navigation li ul { - position: absolute; - bottom: 100%; - right: 0; - z-index: 1; -} -.ie7 .rtl .main-navigation li { - margin-right: auto; - margin-left: 40px; -} -.ie7 .rtl .main-navigation li ul ul ul { - position: relative; - z-index: 1; -} \ No newline at end of file diff --git a/src/DjangoBlog/blog/static/blog/css/nprogress.css b/src/DjangoBlog/blog/static/blog/css/nprogress.css deleted file mode 100644 index 90c7b6c..0000000 --- a/src/DjangoBlog/blog/static/blog/css/nprogress.css +++ /dev/null @@ -1,74 +0,0 @@ -/* Make clicks pass-through */ -#nprogress { - pointer-events: none; -} - -#nprogress .bar { - background: red; - - position: fixed; - z-index: 1031; - top: 0; - left: 0; - - width: 100%; - height: 2px; -} - -/* Fancy blur effect */ -#nprogress .peg { - display: block; - position: absolute; - right: 0px; - width: 100px; - height: 100%; - box-shadow: 0 0 10px #29d, 0 0 5px #29d; - opacity: 1.0; - - -webkit-transform: rotate(3deg) translate(0px, -4px); - -ms-transform: rotate(3deg) translate(0px, -4px); - transform: rotate(3deg) translate(0px, -4px); -} - -/* Remove these to get rid of the spinner */ -#nprogress .spinner { - display: block; - position: fixed; - z-index: 1031; - top: 15px; - right: 15px; -} - -#nprogress .spinner-icon { - width: 18px; - height: 18px; - box-sizing: border-box; - - border: solid 2px transparent; - border-top-color: red; - border-left-color: red; - border-radius: 50%; - - -webkit-animation: nprogress-spinner 400ms linear infinite; - animation: nprogress-spinner 400ms linear infinite; -} - -.nprogress-custom-parent { - overflow: hidden; - position: relative; -} - -.nprogress-custom-parent #nprogress .spinner, -.nprogress-custom-parent #nprogress .bar { - position: absolute; -} - -@-webkit-keyframes nprogress-spinner { - 0% { -webkit-transform: rotate(0deg); } - 100% { -webkit-transform: rotate(360deg); } -} -@keyframes nprogress-spinner { - 0% { transform: rotate(0deg); } - 100% { transform: rotate(360deg); } -} - diff --git a/src/DjangoBlog/blog/static/blog/css/oauth_style.css b/src/DjangoBlog/blog/static/blog/css/oauth_style.css deleted file mode 100644 index 8af78af..0000000 --- a/src/DjangoBlog/blog/static/blog/css/oauth_style.css +++ /dev/null @@ -1,305 +0,0 @@ - -.icon-sn-google { - background-position: 0 -28px; -} - -.icon-sn-bg-google { - background-color: #4285f4; - background-position: 0 0; -} - -.fa-sn-google { - color: #4285f4; -} - -.icon-sn-github { - background-position: -28px -28px; -} - -.icon-sn-bg-github { - background-color: #333; - background-position: -28px 0; -} - -.fa-sn-github { - color: #333; -} - -.icon-sn-weibo { - background-position: -56px -28px; -} - -.icon-sn-bg-weibo { - background-color: #e90d24; - background-position: -56px 0; -} - -.fa-sn-weibo { - color: #e90d24; -} - -.icon-sn-qq { - background-position: -84px -28px; -} - -.icon-sn-bg-qq { - background-color: #0098e6; - background-position: -84px 0; -} - -.fa-sn-qq { - color: #0098e6; -} - -.icon-sn-twitter { - background-position: -112px -28px; -} - -.icon-sn-bg-twitter { - background-color: #50abf1; - background-position: -112px 0; -} - -.fa-sn-twitter { - color: #50abf1; -} - -.icon-sn-facebook { - background-position: -140px -28px; -} - -.icon-sn-bg-facebook { - background-color: #4862a3; - background-position: -140px 0; -} - -.fa-sn-facebook { - color: #4862a3; -} - -.icon-sn-renren { - background-position: -168px -28px; -} - -.icon-sn-bg-renren { - background-color: #197bc8; - background-position: -168px 0; -} - -.fa-sn-renren { - color: #197bc8; -} - -.icon-sn-tqq { - background-position: -196px -28px; -} - -.icon-sn-bg-tqq { - background-color: #1f9ed2; - background-position: -196px 0; -} - -.fa-sn-tqq { - color: #1f9ed2; -} - -.icon-sn-douban { - background-position: -224px -28px; -} - -.icon-sn-bg-douban { - background-color: #279738; - background-position: -224px 0; -} - -.fa-sn-douban { - color: #279738; -} - -.icon-sn-weixin { - background-position: -252px -28px; -} - -.icon-sn-bg-weixin { - background-color: #00b500; - background-position: -252px 0; -} - -.fa-sn-weixin { - color: #00b500; -} - -.icon-sn-dotted { - background-position: -280px -28px; -} - -.icon-sn-bg-dotted { - background-color: #eee; - background-position: -280px 0; -} - -.fa-sn-dotted { - color: #eee; -} - -.icon-sn-site { - background-position: -308px -28px; -} - -.icon-sn-bg-site { - background-color: #00b500; - background-position: -308px 0; -} - -.fa-sn-site { - color: #00b500; -} - -.icon-sn-linkedin { - background-position: -336px -28px; -} - -.icon-sn-bg-linkedin { - background-color: #0077b9; - background-position: -336px 0; -} - -.fa-sn-linkedin { - color: #0077b9; -} - -[class*=icon-sn-] { - display: inline-block; - background-image: url('../img/icon-sn.svg'); - background-repeat: no-repeat; - width: 28px; - height: 28px; - vertical-align: middle; - background-size: auto 56px; -} - -[class*=icon-sn-]:hover { - opacity: .8; - filter: alpha(opacity=80); -} - -.btn-sn-google { - background: #4285f4; -} - -.btn-sn-google:active, .btn-sn-google:focus, .btn-sn-google:hover { - background: #2a75f3; -} - -.btn-sn-github { - background: #333; -} - -.btn-sn-github:active, .btn-sn-github:focus, .btn-sn-github:hover { - background: #262626; -} - -.btn-sn-weibo { - background: #e90d24; -} - -.btn-sn-weibo:active, .btn-sn-weibo:focus, .btn-sn-weibo:hover { - background: #d10c20; -} - -.btn-sn-qq { - background: #0098e6; -} - -.btn-sn-qq:active, .btn-sn-qq:focus, .btn-sn-qq:hover { - background: #0087cd; -} - -.btn-sn-twitter { - background: #50abf1; -} - -.btn-sn-twitter:active, .btn-sn-twitter:focus, .btn-sn-twitter:hover { - background: #38a0ef; -} - -.btn-sn-facebook { - background: #4862a3; -} - -.btn-sn-facebook:active, .btn-sn-facebook:focus, .btn-sn-facebook:hover { - background: #405791; -} - -.btn-sn-renren { - background: #197bc8; -} - -.btn-sn-renren:active, .btn-sn-renren:focus, .btn-sn-renren:hover { - background: #166db1; -} - -.btn-sn-tqq { - background: #1f9ed2; -} - -.btn-sn-tqq:active, .btn-sn-tqq:focus, .btn-sn-tqq:hover { - background: #1c8dbc; -} - -.btn-sn-douban { - background: #279738; -} - -.btn-sn-douban:active, .btn-sn-douban:focus, .btn-sn-douban:hover { - background: #228330; -} - -.btn-sn-weixin { - background: #00b500; -} - -.btn-sn-weixin:active, .btn-sn-weixin:focus, .btn-sn-weixin:hover { - background: #009c00; -} - -.btn-sn-dotted { - background: #eee; -} - -.btn-sn-dotted:active, .btn-sn-dotted:focus, .btn-sn-dotted:hover { - background: #e1e1e1; -} - -.btn-sn-site { - background: #00b500; -} - -.btn-sn-site:active, .btn-sn-site:focus, .btn-sn-site:hover { - background: #009c00; -} - -.btn-sn-linkedin { - background: #0077b9; -} - -.btn-sn-linkedin:active, .btn-sn-linkedin:focus, .btn-sn-linkedin:hover { - background: #0067a0; -} - -[class*=btn-sn-], [class*=btn-sn-]:active, [class*=btn-sn-]:focus, [class*=btn-sn-]:hover { - border: none; - color: #fff; -} - -.btn-sn-more { - padding: 0; -} - -.btn-sn-more, .btn-sn-more:active, .btn-sn-more:hover { - box-shadow: none; -} - -[class*=btn-sn-] [class*=icon-sn-] { - background-color: transparent; -} \ No newline at end of file diff --git a/src/DjangoBlog/blog/static/blog/css/style.css b/src/DjangoBlog/blog/static/blog/css/style.css deleted file mode 100644 index cdbd790..0000000 --- a/src/DjangoBlog/blog/static/blog/css/style.css +++ /dev/null @@ -1,2898 +0,0 @@ -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; -} - -/* Clearing floats */ -.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: ""; -} - - -/* =Repeatable patterns --------------------------------------------------------------- */ - -/* Small headers */ -.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; -} - -/* Shared Post Format styling */ -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; -} - -/* Form fields, general styles first */ -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; -} - -/* Reset non-text input types */ -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; -} - -/* Buttons */ -.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; -} - -/* Responsive images */ -.entry-content img, -.comment-content img, -.widget img { - max-width: 100%; /* Fluid images for posts, comments, and widgets */ -} - -img[class*="align"], -img[class*="wp-image-"], -img[class*="attachment-"] { - height: auto; /* Make sure images with WordPress-added height and width attributes are scaled correctly */ -} - -img.size-full, -img.size-large, -img.header-image, -img.wp-post-image { - max-width: 100%; - height: auto; /* Make sure images with WordPress-added height and width attributes are scaled correctly */ -} - -/* Make sure videos and embeds fit their containers */ -embed, -iframe, -object, -video { - max-width: 100%; -} - -.entry-content .twitter-tweet-rendered { - max-width: 100% !important; /* Override the Twitter embed fixed width */ -} - -/* Images */ -.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 { - /* Add fancy borders to all WordPress-added images but not things like badges and icons and the like */ - border-radius: 3px; - box-shadow: 0 1px 4px rgba(0, 0, 0, 0.2); -} - -.wp-caption { - max-width: 100%; /* Keep wide captions from overflowing their container. */ - 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%; -} - -/* Navigation */ -.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 profiles */ -.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; -} - - -/* =Basic structure --------------------------------------------------------------- */ - -/* Body, links, basics */ -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 */ -.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; /* Above WP toolbar */ -} - -/* Page structure */ -.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; -} - -/* Header */ -.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; -} - -/* Navigation Menu */ -.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; -} - -/* Banner */ -section[role="banner"] { - margin-bottom: 48px; - margin-bottom: 3.428571429rem; -} - -/* Sidebar */ -.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%; /* define a width to avoid dropping a wider submit button */ -} - -/* Footer */ -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'; -} - - -/* =Main content and comment content --------------------------------------------------------------- */ - -.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; -} - - -/* =Archives --------------------------------------------------------------- */ - -.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; -} - -/* =Single audio/video attachment view --------------------------------------------------------------- */ - -.attachment .entry-content .mejs-audio { - max-width: 400px; -} - -.attachment .entry-content .mejs-container { - margin-bottom: 24px; -} - - -/* =Single image attachment view --------------------------------------------------------------- */ - -.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; -} - - -/* =Aside post format --------------------------------------------------------------- */ - -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; -} - - -/* =Post formats --------------------------------------------------------------- */ - -/* Image posts */ -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; -} - -/* Link posts */ -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; -} - -/* Quote posts */ -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; -} - -/* Status posts */ -.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 --------------------------------------------------------------- */ - -.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; -} - -/* Comment form */ -#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; -} - - -/* =Front page template --------------------------------------------------------------- */ - -.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; -} - - -/* =Widgets --------------------------------------------------------------- */ - -.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%; -} - -/* =Plugins ------------------------------------------------ */ - -img#wpstats { - display: block; - margin: 0 auto 24px; - margin: 0 auto 1.714285714rem; -} - - -/* =Media queries --------------------------------------------------------------- */ - -/* Does the same thing as , - * but in the future W3C standard way. -ms- prefix is required for IE10+ to - * render responsive styling in Windows 8 "snapped" views; IE10+ does not honor - * the meta tag. See https://core.trac.wordpress.org/ticket/25888. - */ -@-ms-viewport { - width: device-width; -} - -@viewport { - width: device-width; -} - -/* Minimum width of 600 pixels. */ -@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; - } -} - -/* Minimum width of 960 pixels. */ -@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; - } -} - - -/* =Print ------------------------------------------------ */ - -@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) "] "; /* Show URLs */ - } - - 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; - } - - /* Comments */ - .commentlist > li.comment { - background: none; - position: relative; - width: auto; - } - - - - .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 -/* ------------------------------------------------------------------------- */ -#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-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-awaiting-moderation { - 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 .fn, .pinglist .ping-link { - color: #444; - font-size: 13px; - font-style: normal; - font-weight: 600; -} - -.commentlist .says { - 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; -} - -.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; -} - -/* pings */ -.pinglist li { - padding-left: 0; -} - -/* comment text */ -.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; -} - -/* post author & admin comment */ -.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"; /* star for admin */ -} - -.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%; -} - -/* child comment */ -.commentlist li ul { -} - -.commentlist li li { - margin: 0; - 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 .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; -} - -/* comments : nav -/* ------------------------------------ */ -.comments-nav { - margin-bottom: 20px; -} - -.comments-nav a { - font-weight: 600; -} - -.comments-nav .nav-previous { - float: left; -} - -.comments-nav .nav-next { - float: right; -} - -/* comments : form -/* ------------------------------------ */ -.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; - /*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/memtYaGs126MiZpBA-UFUIcVXSCEkx2cmqvXlWqW106F15M.woff2 b/src/DjangoBlog/blog/static/blog/fonts/memtYaGs126MiZpBA-UFUIcVXSCEkx2cmqvXlWqW106F15M.woff2 deleted file mode 100644 index 0fb066c..0000000 Binary files a/src/DjangoBlog/blog/static/blog/fonts/memtYaGs126MiZpBA-UFUIcVXSCEkx2cmqvXlWqW106F15M.woff2 and /dev/null differ diff --git a/src/DjangoBlog/blog/static/blog/fonts/memtYaGs126MiZpBA-UFUIcVXSCEkx2cmqvXlWqWt06F15M.woff2 b/src/DjangoBlog/blog/static/blog/fonts/memtYaGs126MiZpBA-UFUIcVXSCEkx2cmqvXlWqWt06F15M.woff2 deleted file mode 100644 index bc2aea0..0000000 Binary files a/src/DjangoBlog/blog/static/blog/fonts/memtYaGs126MiZpBA-UFUIcVXSCEkx2cmqvXlWqWt06F15M.woff2 and /dev/null differ diff --git a/src/DjangoBlog/blog/static/blog/fonts/memtYaGs126MiZpBA-UFUIcVXSCEkx2cmqvXlWqWtE6F15M.woff2 b/src/DjangoBlog/blog/static/blog/fonts/memtYaGs126MiZpBA-UFUIcVXSCEkx2cmqvXlWqWtE6F15M.woff2 deleted file mode 100644 index fcce594..0000000 Binary files a/src/DjangoBlog/blog/static/blog/fonts/memtYaGs126MiZpBA-UFUIcVXSCEkx2cmqvXlWqWtE6F15M.woff2 and /dev/null differ diff --git a/src/DjangoBlog/blog/static/blog/fonts/memtYaGs126MiZpBA-UFUIcVXSCEkx2cmqvXlWqWtU6F15M.woff2 b/src/DjangoBlog/blog/static/blog/fonts/memtYaGs126MiZpBA-UFUIcVXSCEkx2cmqvXlWqWtU6F15M.woff2 deleted file mode 100644 index ffc8e9c..0000000 Binary files a/src/DjangoBlog/blog/static/blog/fonts/memtYaGs126MiZpBA-UFUIcVXSCEkx2cmqvXlWqWtU6F15M.woff2 and /dev/null differ diff --git a/src/DjangoBlog/blog/static/blog/fonts/memtYaGs126MiZpBA-UFUIcVXSCEkx2cmqvXlWqWtk6F15M.woff2 b/src/DjangoBlog/blog/static/blog/fonts/memtYaGs126MiZpBA-UFUIcVXSCEkx2cmqvXlWqWtk6F15M.woff2 deleted file mode 100644 index 6375e9c..0000000 Binary files a/src/DjangoBlog/blog/static/blog/fonts/memtYaGs126MiZpBA-UFUIcVXSCEkx2cmqvXlWqWtk6F15M.woff2 and /dev/null differ diff --git a/src/DjangoBlog/blog/static/blog/fonts/memtYaGs126MiZpBA-UFUIcVXSCEkx2cmqvXlWqWu06F15M.woff2 b/src/DjangoBlog/blog/static/blog/fonts/memtYaGs126MiZpBA-UFUIcVXSCEkx2cmqvXlWqWu06F15M.woff2 deleted file mode 100644 index 2e849f6..0000000 Binary files a/src/DjangoBlog/blog/static/blog/fonts/memtYaGs126MiZpBA-UFUIcVXSCEkx2cmqvXlWqWu06F15M.woff2 and /dev/null differ diff --git a/src/DjangoBlog/blog/static/blog/fonts/memtYaGs126MiZpBA-UFUIcVXSCEkx2cmqvXlWqWuU6F.woff2 b/src/DjangoBlog/blog/static/blog/fonts/memtYaGs126MiZpBA-UFUIcVXSCEkx2cmqvXlWqWuU6F.woff2 deleted file mode 100644 index 5de3fea..0000000 Binary files a/src/DjangoBlog/blog/static/blog/fonts/memtYaGs126MiZpBA-UFUIcVXSCEkx2cmqvXlWqWuU6F.woff2 and /dev/null differ diff --git a/src/DjangoBlog/blog/static/blog/fonts/memtYaGs126MiZpBA-UFUIcVXSCEkx2cmqvXlWqWuk6F15M.woff2 b/src/DjangoBlog/blog/static/blog/fonts/memtYaGs126MiZpBA-UFUIcVXSCEkx2cmqvXlWqWuk6F15M.woff2 deleted file mode 100644 index e5c936b..0000000 Binary files a/src/DjangoBlog/blog/static/blog/fonts/memtYaGs126MiZpBA-UFUIcVXSCEkx2cmqvXlWqWuk6F15M.woff2 and /dev/null differ diff --git a/src/DjangoBlog/blog/static/blog/fonts/memtYaGs126MiZpBA-UFUIcVXSCEkx2cmqvXlWqWvU6F15M.woff2 b/src/DjangoBlog/blog/static/blog/fonts/memtYaGs126MiZpBA-UFUIcVXSCEkx2cmqvXlWqWvU6F15M.woff2 deleted file mode 100644 index 5cf8aff..0000000 Binary files a/src/DjangoBlog/blog/static/blog/fonts/memtYaGs126MiZpBA-UFUIcVXSCEkx2cmqvXlWqWvU6F15M.woff2 and /dev/null differ diff --git a/src/DjangoBlog/blog/static/blog/fonts/memtYaGs126MiZpBA-UFUIcVXSCEkx2cmqvXlWqWxU6F15M.woff2 b/src/DjangoBlog/blog/static/blog/fonts/memtYaGs126MiZpBA-UFUIcVXSCEkx2cmqvXlWqWxU6F15M.woff2 deleted file mode 100644 index bdc12e8..0000000 Binary files a/src/DjangoBlog/blog/static/blog/fonts/memtYaGs126MiZpBA-UFUIcVXSCEkx2cmqvXlWqWxU6F15M.woff2 and /dev/null differ diff --git a/src/DjangoBlog/blog/static/blog/fonts/memvYaGs126MiZpBA-UvWbX2vVnXBbObj2OVTS-muw.woff2 b/src/DjangoBlog/blog/static/blog/fonts/memvYaGs126MiZpBA-UvWbX2vVnXBbObj2OVTS-muw.woff2 deleted file mode 100644 index b5d54e7..0000000 Binary files a/src/DjangoBlog/blog/static/blog/fonts/memvYaGs126MiZpBA-UvWbX2vVnXBbObj2OVTS-muw.woff2 and /dev/null differ diff --git a/src/DjangoBlog/blog/static/blog/fonts/memvYaGs126MiZpBA-UvWbX2vVnXBbObj2OVTS2mu1aB.woff2 b/src/DjangoBlog/blog/static/blog/fonts/memvYaGs126MiZpBA-UvWbX2vVnXBbObj2OVTS2mu1aB.woff2 deleted file mode 100644 index bed5b67..0000000 Binary files a/src/DjangoBlog/blog/static/blog/fonts/memvYaGs126MiZpBA-UvWbX2vVnXBbObj2OVTS2mu1aB.woff2 and /dev/null differ diff --git a/src/DjangoBlog/blog/static/blog/fonts/memvYaGs126MiZpBA-UvWbX2vVnXBbObj2OVTSCmu1aB.woff2 b/src/DjangoBlog/blog/static/blog/fonts/memvYaGs126MiZpBA-UvWbX2vVnXBbObj2OVTSCmu1aB.woff2 deleted file mode 100644 index 9164ccb..0000000 Binary files a/src/DjangoBlog/blog/static/blog/fonts/memvYaGs126MiZpBA-UvWbX2vVnXBbObj2OVTSCmu1aB.woff2 and /dev/null differ diff --git a/src/DjangoBlog/blog/static/blog/fonts/memvYaGs126MiZpBA-UvWbX2vVnXBbObj2OVTSGmu1aB.woff2 b/src/DjangoBlog/blog/static/blog/fonts/memvYaGs126MiZpBA-UvWbX2vVnXBbObj2OVTSGmu1aB.woff2 deleted file mode 100644 index 08bed85..0000000 Binary files a/src/DjangoBlog/blog/static/blog/fonts/memvYaGs126MiZpBA-UvWbX2vVnXBbObj2OVTSGmu1aB.woff2 and /dev/null differ diff --git a/src/DjangoBlog/blog/static/blog/fonts/memvYaGs126MiZpBA-UvWbX2vVnXBbObj2OVTSKmu1aB.woff2 b/src/DjangoBlog/blog/static/blog/fonts/memvYaGs126MiZpBA-UvWbX2vVnXBbObj2OVTSKmu1aB.woff2 deleted file mode 100644 index 307b214..0000000 Binary files a/src/DjangoBlog/blog/static/blog/fonts/memvYaGs126MiZpBA-UvWbX2vVnXBbObj2OVTSKmu1aB.woff2 and /dev/null differ diff --git a/src/DjangoBlog/blog/static/blog/fonts/memvYaGs126MiZpBA-UvWbX2vVnXBbObj2OVTSOmu1aB.woff2 b/src/DjangoBlog/blog/static/blog/fonts/memvYaGs126MiZpBA-UvWbX2vVnXBbObj2OVTSOmu1aB.woff2 deleted file mode 100644 index 0b0b3a4..0000000 Binary files a/src/DjangoBlog/blog/static/blog/fonts/memvYaGs126MiZpBA-UvWbX2vVnXBbObj2OVTSOmu1aB.woff2 and /dev/null differ diff --git a/src/DjangoBlog/blog/static/blog/fonts/memvYaGs126MiZpBA-UvWbX2vVnXBbObj2OVTSumu1aB.woff2 b/src/DjangoBlog/blog/static/blog/fonts/memvYaGs126MiZpBA-UvWbX2vVnXBbObj2OVTSumu1aB.woff2 deleted file mode 100644 index 4bce1d0..0000000 Binary files a/src/DjangoBlog/blog/static/blog/fonts/memvYaGs126MiZpBA-UvWbX2vVnXBbObj2OVTSumu1aB.woff2 and /dev/null differ diff --git a/src/DjangoBlog/blog/static/blog/fonts/memvYaGs126MiZpBA-UvWbX2vVnXBbObj2OVTSymu1aB.woff2 b/src/DjangoBlog/blog/static/blog/fonts/memvYaGs126MiZpBA-UvWbX2vVnXBbObj2OVTSymu1aB.woff2 deleted file mode 100644 index 5bd7b8f..0000000 Binary files a/src/DjangoBlog/blog/static/blog/fonts/memvYaGs126MiZpBA-UvWbX2vVnXBbObj2OVTSymu1aB.woff2 and /dev/null differ diff --git a/src/DjangoBlog/blog/static/blog/fonts/memvYaGs126MiZpBA-UvWbX2vVnXBbObj2OVTUGmu1aB.woff2 b/src/DjangoBlog/blog/static/blog/fonts/memvYaGs126MiZpBA-UvWbX2vVnXBbObj2OVTUGmu1aB.woff2 deleted file mode 100644 index b969602..0000000 Binary files a/src/DjangoBlog/blog/static/blog/fonts/memvYaGs126MiZpBA-UvWbX2vVnXBbObj2OVTUGmu1aB.woff2 and /dev/null differ diff --git a/src/DjangoBlog/blog/static/blog/fonts/memvYaGs126MiZpBA-UvWbX2vVnXBbObj2OVTVOmu1aB.woff2 b/src/DjangoBlog/blog/static/blog/fonts/memvYaGs126MiZpBA-UvWbX2vVnXBbObj2OVTVOmu1aB.woff2 deleted file mode 100644 index a804b10..0000000 Binary files a/src/DjangoBlog/blog/static/blog/fonts/memvYaGs126MiZpBA-UvWbX2vVnXBbObj2OVTVOmu1aB.woff2 and /dev/null differ diff --git a/src/DjangoBlog/blog/static/blog/fonts/open-sans.css b/src/DjangoBlog/blog/static/blog/fonts/open-sans.css deleted file mode 100644 index e6dd4a9..0000000 --- a/src/DjangoBlog/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/DjangoBlog/blog/static/blog/img/avatar.png b/src/DjangoBlog/blog/static/blog/img/avatar.png deleted file mode 100644 index 320756f..0000000 Binary files a/src/DjangoBlog/blog/static/blog/img/avatar.png and /dev/null differ diff --git a/src/DjangoBlog/blog/static/blog/img/icon-sn.svg b/src/DjangoBlog/blog/static/blog/img/icon-sn.svg deleted file mode 100644 index 2c2da0a..0000000 --- a/src/DjangoBlog/blog/static/blog/img/icon-sn.svg +++ /dev/null @@ -1 +0,0 @@ -icon-sn \ No newline at end of file diff --git a/src/DjangoBlog/blog/static/blog/js/blog.js b/src/DjangoBlog/blog/static/blog/js/blog.js deleted file mode 100644 index c50dd7d..0000000 --- a/src/DjangoBlog/blog/static/blog/js/blog.js +++ /dev/null @@ -1,91 +0,0 @@ -/** - * Created by liangliang on 2016/11/20. - */ - - -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); -//Increment -var interval = setInterval(function () { - NProgress.inc(); -}, 1000); -$(document).ready(function () { - NProgress.done(); - clearInterval(interval); -}); - - -/** 侧边栏回到顶部 */ -var rocket = $('#rocket'); - -$(window).on('scroll', debounce(slideTopSet, 300)); - -function debounce(func, wait) { - var timeout; - return function () { - clearTimeout(timeout); - timeout = setTimeout(func, wait); - }; -} - -function slideTopSet() { - var top = $(document).scrollTop(); - - if (top > 200) { - rocket.addClass('show'); - } else { - rocket.removeClass('show'); - } -} - -$(document).on('click', '#rocket', function (event) { - rocket.addClass('move'); - $('body, html').animate({ - scrollTop: 0 - }, 800); -}); -$(document).on('animationEnd', function () { - setTimeout(function () { - rocket.removeClass('move'); - }, 400); - -}); -$(document).on('webkitAnimationEnd', function () { - setTimeout(function () { - rocket.removeClass('move'); - }, 400); -}); - - -window.onload = function () { - var replyLinks = document.querySelectorAll(".comment-reply-link"); - for (var i = 0; i < replyLinks.length; i++) { - replyLinks[i].onclick = function () { - var pk = this.getAttribute("data-pk"); - do_reply(pk); - }; - } -}; - -// $(document).ready(function () { -// var form = $('#i18n-form'); -// var selector = $('.i18n-select'); -// selector.on('change', function () { -// form.submit(); -// }); -// }); \ No newline at end of file diff --git a/src/DjangoBlog/blog/static/blog/js/html5.js b/src/DjangoBlog/blog/static/blog/js/html5.js deleted file mode 100644 index 6168aac..0000000 --- a/src/DjangoBlog/blog/static/blog/js/html5.js +++ /dev/null @@ -1,8 +0,0 @@ -/* - HTML5 Shiv v3.7.0 | @afarkas @jdalton @jon_neal @rem | MIT/GPL2 Licensed -*/ -(function(l,f){function m(){var a=e.elements;return"string"==typeof a?a.split(" "):a}function i(a){var b=n[a[o]];b||(b={},h++,a[o]=h,n[h]=b);return b}function p(a,b,c){b||(b=f);if(g)return b.createElement(a);c||(c=i(b));b=c.cache[a]?c.cache[a].cloneNode():r.test(a)?(c.cache[a]=c.createElem(a)).cloneNode():c.createElem(a);return b.canHaveChildren&&!s.test(a)?c.frag.appendChild(b):b}function t(a,b){if(!b.cache)b.cache={},b.createElem=a.createElement,b.createFrag=a.createDocumentFragment,b.frag=b.createFrag(); -a.createElement=function(c){return!e.shivMethods?b.createElem(c):p(c,a,b)};a.createDocumentFragment=Function("h,f","return function(){var n=f.cloneNode(),c=n.createElement;h.shivMethods&&("+m().join().replace(/[\w\-]+/g,function(a){b.createElem(a);b.frag.createElement(a);return'c("'+a+'")'})+");return n}")(e,b.frag)}function q(a){a||(a=f);var b=i(a);if(e.shivCSS&&!j&&!b.hasCSS){var c,d=a;c=d.createElement("p");d=d.getElementsByTagName("head")[0]||d.documentElement;c.innerHTML="x"; -c=d.insertBefore(c.lastChild,d.firstChild);b.hasCSS=!!c}g||t(a,b);return a}var k=l.html5||{},s=/^<|^(?:button|map|select|textarea|object|iframe|option|optgroup)$/i,r=/^(?:a|b|code|div|fieldset|h1|h2|h3|h4|h5|h6|i|label|li|ol|p|q|span|strong|style|table|tbody|td|th|tr|ul)$/i,j,o="_html5shiv",h=0,n={},g;(function(){try{var a=f.createElement("a");a.innerHTML="";j="hidden"in a;var b;if(!(b=1==a.childNodes.length)){f.createElement("a");var c=f.createDocumentFragment();b="undefined"==typeof c.cloneNode|| -"undefined"==typeof c.createDocumentFragment||"undefined"==typeof c.createElement}g=b}catch(d){g=j=!0}})();var e={elements:k.elements||"abbr article aside audio bdi canvas data datalist details dialog figcaption figure footer header hgroup main mark meter nav output progress section summary template time video",version:"3.7.0",shivCSS:!1!==k.shivCSS,supportsUnknownElements:g,shivMethods:!1!==k.shivMethods,type:"default",shivDocument:q,createElement:p,createDocumentFragment:function(a,b){a||(a=f); -if(g)return a.createDocumentFragment();for(var b=b||i(a),c=b.frag.cloneNode(),d=0,e=m(),h=e.length;d+~]|"+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 { - 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/DjangoBlog/blog/static/blog/js/navigation.js b/src/DjangoBlog/blog/static/blog/js/navigation.js deleted file mode 100644 index f7141bf..0000000 --- a/src/DjangoBlog/blog/static/blog/js/navigation.js +++ /dev/null @@ -1,55 +0,0 @@ -/** - * Handles toggling the navigation menu for small screens and - * accessibility for submenu items. - */ -( function() { - var nav = document.getElementById( 'site-navigation' ), button, menu; - if ( ! nav ) { - return; - } - - button = nav.getElementsByTagName( 'button' )[0]; - menu = nav.getElementsByTagName( 'ul' )[0]; - if ( ! button ) { - return; - } - - // Hide button if menu is missing or empty. - if ( ! menu || ! menu.childNodes.length ) { - button.style.display = 'none'; - return; - } - - button.onclick = function() { - if ( -1 === menu.className.indexOf( 'nav-menu' ) ) { - menu.className = 'nav-menu'; - } - - if ( -1 !== button.className.indexOf( 'toggled-on' ) ) { - button.className = button.className.replace( ' toggled-on', '' ); - menu.className = menu.className.replace( ' toggled-on', '' ); - } else { - button.className += ' toggled-on'; - menu.className += ' toggled-on'; - } - }; -} )(); - -// Better focus for hidden submenu items for accessibility. -( function( $ ) { - $( '.main-navigation' ).find( 'a' ).on( 'focus.twentytwelve blur.twentytwelve', function() { - $( this ).parents( '.menu-item, .page_item' ).toggleClass( 'focus' ); - } ); - - if ( 'ontouchstart' in window ) { - $('body').on( 'touchstart.twentytwelve', '.menu-item-has-children > 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 ); diff --git a/src/DjangoBlog/blog/static/blog/js/nprogress.js b/src/DjangoBlog/blog/static/blog/js/nprogress.js deleted file mode 100644 index d29c2aa..0000000 --- a/src/DjangoBlog/blog/static/blog/js/nprogress.js +++ /dev/null @@ -1,480 +0,0 @@ -/* NProgress, (c) 2013, 2014 Rico Sta. Cruz - http://ricostacruz.com/nprogress - * @license MIT */ - -;(function(root, factory) { - - if (typeof define === 'function' && define.amd) { - define(factory); - } else if (typeof exports === 'object') { - module.exports = factory(); - } else { - root.NProgress = factory(); - } - -})(this, function() { - var NProgress = {}; - - NProgress.version = '0.2.0'; - - var Settings = NProgress.settings = { - minimum: 0.08, - easing: 'linear', - positionUsing: '', - speed: 200, - trickle: true, - trickleSpeed: 200, - showSpinner: true, - barSelector: '[role="bar"]', - spinnerSelector: '[role="spinner"]', - parent: 'body', - template: '
' - }; - - /** - * Updates configuration. - * - * NProgress.configure({ - * minimum: 0.1 - * }); - */ - NProgress.configure = function(options) { - var key, value; - for (key in options) { - value = options[key]; - if (value !== undefined && options.hasOwnProperty(key)) Settings[key] = value; - } - - return this; - }; - - /** - * Last number. - */ - - NProgress.status = null; - - /** - * Sets the progress bar status, where `n` is a number from `0.0` to `1.0`. - * - * NProgress.set(0.4); - * NProgress.set(1.0); - */ - - NProgress.set = function(n) { - var started = NProgress.isStarted(); - - n = clamp(n, Settings.minimum, 1); - NProgress.status = (n === 1 ? null : n); - - var progress = NProgress.render(!started), - bar = progress.querySelector(Settings.barSelector), - speed = Settings.speed, - ease = Settings.easing; - - progress.offsetWidth; /* Repaint */ - - queue(function(next) { - // Set positionUsing if it hasn't already been set - if (Settings.positionUsing === '') Settings.positionUsing = NProgress.getPositioningCSS(); - - // Add transition - css(bar, barPositionCSS(n, speed, ease)); - - if (n === 1) { - // Fade out - css(progress, { - transition: 'none', - opacity: 1 - }); - progress.offsetWidth; /* Repaint */ - - setTimeout(function() { - css(progress, { - transition: 'all ' + speed + 'ms linear', - opacity: 0 - }); - setTimeout(function() { - NProgress.remove(); - next(); - }, speed); - }, speed); - } else { - setTimeout(next, speed); - } - }); - - return this; - }; - - NProgress.isStarted = function() { - return typeof NProgress.status === 'number'; - }; - - /** - * Shows the progress bar. - * This is the same as setting the status to 0%, except that it doesn't go backwards. - * - * NProgress.start(); - * - */ - NProgress.start = function() { - if (!NProgress.status) NProgress.set(0); - - var work = function() { - setTimeout(function() { - if (!NProgress.status) return; - NProgress.trickle(); - work(); - }, Settings.trickleSpeed); - }; - - if (Settings.trickle) work(); - - return this; - }; - - /** - * Hides the progress bar. - * This is the *sort of* the same as setting the status to 100%, with the - * difference being `done()` makes some placebo effect of some realistic motion. - * - * NProgress.done(); - * - * If `true` is passed, it will show the progress bar even if its hidden. - * - * NProgress.done(true); - */ - - NProgress.done = function(force) { - if (!force && !NProgress.status) return this; - - return NProgress.inc(0.3 + 0.5 * Math.random()).set(1); - }; - - /** - * Increments by a random amount. - */ - - NProgress.inc = function(amount) { - var n = NProgress.status; - - if (!n) { - return NProgress.start(); - } else if(n > 1) { - - } else { - if (typeof amount !== 'number') { - if (n >= 0 && n < 0.2) { amount = 0.1; } - else if (n >= 0.2 && n < 0.5) { amount = 0.04; } - else if (n >= 0.5 && n < 0.8) { amount = 0.02; } - else if (n >= 0.8 && n < 0.99) { amount = 0.005; } - else { amount = 0; } - } - - n = clamp(n + amount, 0, 0.994); - return NProgress.set(n); - } - }; - - NProgress.trickle = function() { - return NProgress.inc(); - }; - - /** - * Waits for all supplied jQuery promises and - * increases the progress as the promises resolve. - * - * @param $promise jQUery Promise - */ - (function() { - var initial = 0, current = 0; - - NProgress.promise = function($promise) { - if (!$promise || $promise.state() === "resolved") { - return this; - } - - if (current === 0) { - NProgress.start(); - } - - initial++; - current++; - - $promise.always(function() { - current--; - if (current === 0) { - initial = 0; - NProgress.done(); - } else { - NProgress.set((initial - current) / initial); - } - }); - - return this; - }; - - })(); - - /** - * (Internal) renders the progress bar markup based on the `template` - * setting. - */ - - NProgress.render = function(fromStart) { - if (NProgress.isRendered()) return document.getElementById('nprogress'); - - addClass(document.documentElement, 'nprogress-busy'); - - var progress = document.createElement('div'); - progress.id = 'nprogress'; - progress.innerHTML = Settings.template; - - var bar = progress.querySelector(Settings.barSelector), - perc = fromStart ? '-100' : toBarPerc(NProgress.status || 0), - parent = document.querySelector(Settings.parent), - spinner; - - css(bar, { - transition: 'all 0 linear', - transform: 'translate3d(' + perc + '%,0,0)' - }); - - if (!Settings.showSpinner) { - spinner = progress.querySelector(Settings.spinnerSelector); - spinner && removeElement(spinner); - } - - if (parent != document.body) { - addClass(parent, 'nprogress-custom-parent'); - } - - parent.appendChild(progress); - return progress; - }; - - /** - * Removes the element. Opposite of render(). - */ - - NProgress.remove = function() { - removeClass(document.documentElement, 'nprogress-busy'); - removeClass(document.querySelector(Settings.parent), 'nprogress-custom-parent'); - var progress = document.getElementById('nprogress'); - progress && removeElement(progress); - }; - - /** - * Checks if the progress bar is rendered. - */ - - NProgress.isRendered = function() { - return !!document.getElementById('nprogress'); - }; - - /** - * Determine which positioning CSS rule to use. - */ - - NProgress.getPositioningCSS = function() { - // Sniff on document.body.style - var bodyStyle = document.body.style; - - // Sniff prefixes - var vendorPrefix = ('WebkitTransform' in bodyStyle) ? 'Webkit' : - ('MozTransform' in bodyStyle) ? 'Moz' : - ('msTransform' in bodyStyle) ? 'ms' : - ('OTransform' in bodyStyle) ? 'O' : ''; - - if (vendorPrefix + 'Perspective' in bodyStyle) { - // Modern browsers with 3D support, e.g. Webkit, IE10 - return 'translate3d'; - } else if (vendorPrefix + 'Transform' in bodyStyle) { - // Browsers without 3D support, e.g. IE9 - return 'translate'; - } else { - // Browsers without translate() support, e.g. IE7-8 - return 'margin'; - } - }; - - /** - * Helpers - */ - - function clamp(n, min, max) { - if (n < min) return min; - if (n > max) return max; - return n; - } - - /** - * (Internal) converts a percentage (`0..1`) to a bar translateX - * percentage (`-100%..0%`). - */ - - function toBarPerc(n) { - return (-1 + n) * 100; - } - - - /** - * (Internal) returns the correct CSS for changing the bar's - * position given an n percentage, and speed and ease from Settings - */ - - function barPositionCSS(n, speed, ease) { - var barCSS; - - if (Settings.positionUsing === 'translate3d') { - barCSS = { transform: 'translate3d('+toBarPerc(n)+'%,0,0)' }; - } else if (Settings.positionUsing === 'translate') { - barCSS = { transform: 'translate('+toBarPerc(n)+'%,0)' }; - } else { - barCSS = { 'margin-left': toBarPerc(n)+'%' }; - } - - barCSS.transition = 'all '+speed+'ms '+ease; - - return barCSS; - } - - /** - * (Internal) Queues a function to be executed. - */ - - var queue = (function() { - var pending = []; - - function next() { - var fn = pending.shift(); - if (fn) { - fn(next); - } - } - - return function(fn) { - pending.push(fn); - if (pending.length == 1) next(); - }; - })(); - - /** - * (Internal) Applies css properties to an element, similar to the jQuery - * css method. - * - * While this helper does assist with vendor prefixed property names, it - * does not perform any manipulation of values prior to setting styles. - */ - - var css = (function() { - var cssPrefixes = [ 'Webkit', 'O', 'Moz', 'ms' ], - cssProps = {}; - - function camelCase(string) { - return string.replace(/^-ms-/, 'ms-').replace(/-([\da-z])/gi, function(match, letter) { - return letter.toUpperCase(); - }); - } - - function getVendorProp(name) { - var style = document.body.style; - if (name in style) return name; - - var i = cssPrefixes.length, - capName = name.charAt(0).toUpperCase() + name.slice(1), - vendorName; - while (i--) { - vendorName = cssPrefixes[i] + capName; - if (vendorName in style) return vendorName; - } - - return name; - } - - function getStyleProp(name) { - name = camelCase(name); - return cssProps[name] || (cssProps[name] = getVendorProp(name)); - } - - function applyCss(element, prop, value) { - prop = getStyleProp(prop); - element.style[prop] = value; - } - - return function(element, properties) { - var args = arguments, - prop, - value; - - if (args.length == 2) { - for (prop in properties) { - value = properties[prop]; - if (value !== undefined && properties.hasOwnProperty(prop)) applyCss(element, prop, value); - } - } else { - applyCss(element, args[1], args[2]); - } - } - })(); - - /** - * (Internal) Determines if an element or space separated list of class names contains a class name. - */ - - function hasClass(element, name) { - var list = typeof element == 'string' ? element : classList(element); - return list.indexOf(' ' + name + ' ') >= 0; - } - - /** - * (Internal) Adds a class to an element. - */ - - function addClass(element, name) { - var oldList = classList(element), - newList = oldList + name; - - if (hasClass(oldList, name)) return; - - // Trim the opening space. - element.className = newList.substring(1); - } - - /** - * (Internal) Removes a class from an element. - */ - - function removeClass(element, name) { - var oldList = classList(element), - newList; - - if (!hasClass(element, name)) return; - - // Replace the class name. - newList = oldList.replace(' ' + name + ' ', ' '); - - // Trim the opening and closing spaces. - element.className = newList.substring(1, newList.length - 1); - } - - /** - * (Internal) Gets a space separated list of the class names on the element. - * The list is wrapped with a single space on each end to facilitate finding - * matches within the list. - */ - - function classList(element) { - return (' ' + (element && element.className || '') + ' ').replace(/\s+/gi, ' '); - } - - /** - * (Internal) Removes an element from the DOM. - */ - - function removeElement(element) { - element && element.parentNode && element.parentNode.removeChild(element); - } - - return NProgress; -}); diff --git a/src/DjangoBlog/blog/static/pygments/default.css b/src/DjangoBlog/blog/static/pygments/default.css deleted file mode 100755 index 73e6e49..0000000 --- a/src/DjangoBlog/blog/static/pygments/default.css +++ /dev/null @@ -1,293 +0,0 @@ -.codehilite .hll { - background-color: #ffffcc -} - -.codehilite { - background: #ffffff; -} - -.codehilite .c { - color: #177500 -} - -/* Comment */ -.codehilite .err { - color: #000000 -} - -/* Error */ -.codehilite .k { - color: #A90D91 -} - -/* Keyword */ -.codehilite .l { - color: #1C01CE -} - -/* Literal */ -.codehilite .n { - color: #000000 -} - -/* Name */ -.codehilite .o { - color: #000000 -} - -/* Operator */ -.codehilite .ch { - color: #177500 -} - -/* Comment.Hashbang */ -.codehilite .cm { - color: #177500 -} - -/* Comment.Multiline */ -.codehilite .cp { - color: #633820 -} - -/* Comment.Preproc */ -.codehilite .cpf { - color: #177500 -} - -/* Comment.PreprocFile */ -.codehilite .c1 { - color: #177500 -} - -/* Comment.Single */ -.codehilite .cs { - color: #177500 -} - -/* Comment.Special */ -.codehilite .kc { - color: #A90D91 -} - -/* Keyword.Constant */ -.codehilite .kd { - color: #A90D91 -} - -/* Keyword.Declaration */ -.codehilite .kn { - color: #A90D91 -} - -/* Keyword.Namespace */ -.codehilite .kp { - color: #A90D91 -} - -/* Keyword.Pseudo */ -.codehilite .kr { - color: #A90D91 -} - -/* Keyword.Reserved */ -.codehilite .kt { - color: #A90D91 -} - -/* Keyword.Type */ -.codehilite .ld { - color: #1C01CE -} - -/* Literal.Date */ -.codehilite .m { - color: #1C01CE -} - -/* Literal.Number */ -.codehilite .s { - color: #C41A16 -} - -/* Literal.String */ -.codehilite .na { - color: #836C28 -} - -/* Name.Attribute */ -.codehilite .nb { - color: #A90D91 -} - -/* Name.Builtin */ -.codehilite .nc { - color: #3F6E75 -} - -/* Name.Class */ -.codehilite .no { - color: #000000 -} - -/* Name.Constant */ -.codehilite .nd { - color: #000000 -} - -/* Name.Decorator */ -.codehilite .ni { - color: #000000 -} - -/* Name.Entity */ -.codehilite .ne { - color: #000000 -} - -/* Name.Exception */ -.codehilite .nf { - color: #000000 -} - -/* Name.Function */ -.codehilite .nl { - color: #000000 -} - -/* Name.Label */ -.codehilite .nn { - color: #000000 -} - -/* Name.Namespace */ -.codehilite .nx { - color: #000000 -} - -/* Name.Other */ -.codehilite .py { - color: #000000 -} - -/* Name.Property */ -.codehilite .nt { - color: #000000 -} - -/* Name.Tag */ -.codehilite .nv { - color: #000000 -} - -/* Name.Variable */ -.codehilite .ow { - color: #000000 -} - -/* Operator.Word */ -.codehilite .mb { - color: #1C01CE -} - -/* Literal.Number.Bin */ -.codehilite .mf { - color: #1C01CE -} - -/* Literal.Number.Float */ -.codehilite .mh { - color: #1C01CE -} - -/* Literal.Number.Hex */ -.codehilite .mi { - color: #1C01CE -} - -/* Literal.Number.Integer */ -.codehilite .mo { - color: #1C01CE -} - -/* Literal.Number.Oct */ -.codehilite .sb { - color: #C41A16 -} - -/* Literal.String.Backtick */ -.codehilite .sc { - color: #2300CE -} - -/* Literal.String.Char */ -.codehilite .sd { - color: #C41A16 -} - -/* Literal.String.Doc */ -.codehilite .s2 { - color: #C41A16 -} - -/* Literal.String.Double */ -.codehilite .se { - color: #C41A16 -} - -/* Literal.String.Escape */ -.codehilite .sh { - color: #C41A16 -} - -/* Literal.String.Heredoc */ -.codehilite .si { - color: #C41A16 -} - -/* Literal.String.Interpol */ -.codehilite .sx { - color: #C41A16 -} - -/* Literal.String.Other */ -.codehilite .sr { - color: #C41A16 -} - -/* Literal.String.Regex */ -.codehilite .s1 { - color: #C41A16 -} - -/* Literal.String.Single */ -.codehilite .ss { - color: #C41A16 -} - -/* Literal.String.Symbol */ -.codehilite .bp { - color: #5B269A -} - -/* Name.Builtin.Pseudo */ -.codehilite .vc { - color: #000000 -} - -/* Name.Variable.Class */ -.codehilite .vg { - color: #000000 -} - -/* Name.Variable.Global */ -.codehilite .vi { - color: #000000 -} - -/* Name.Variable.Instance */ -.codehilite .il { - color: #1C01CE -} - -/* Literal.Number.Integer.Long */ \ No newline at end of file diff --git a/src/DjangoBlog/blog/templatetags/__init__.py b/src/DjangoBlog/blog/templatetags/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/src/DjangoBlog/blog/templatetags/blog_tags.py b/src/DjangoBlog/blog/templatetags/blog_tags.py deleted file mode 100644 index 024f2c8..0000000 --- a/src/DjangoBlog/blog/templatetags/blog_tags.py +++ /dev/null @@ -1,554 +0,0 @@ -import hashlib -import logging -import random -import urllib - -from django import template -from django.conf import settings -from django.db.models import Q -from django.shortcuts import get_object_or_404 -from django.template.defaultfilters import stringfilter -from django.templatetags.static import static -from django.urls import reverse -from django.utils.safestring import mark_safe - -from blog.models import Article, Category, Tag, Links, SideBar, LinkShowType -from comments.models import Comment -from djangoblog.utils import CommonMarkdown, sanitize_html -from djangoblog.utils import cache -from djangoblog.utils import get_current_site -from oauth.models import OAuthUser -from djangoblog.plugin_manage import hooks - -logger = logging.getLogger(__name__) - -register = template.Library() - - -@register.simple_tag(takes_context=True) -def head_meta(context): - 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 "" - - -@register.simple_tag -def datetimeformat(data): - try: - return data.strftime(settings.DATE_TIME_FORMAT) - except Exception as e: - logger.error(e) - return "" - - -@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) - - -@register.simple_tag -def get_markdown_toc(content): - from djangoblog.utils import CommonMarkdown - body, toc = CommonMarkdown.get_markdown_with_toc(content) - return mark_safe(toc) - - -@register.filter() -@stringfilter -def comment_markdown(content): - content = CommonMarkdown.get_markdown(content) - return mark_safe(sanitize_html(content)) - - -@register.filter(is_safe=True) -@stringfilter -def truncatechars_content(content): - """ - 获得文章内容的摘要 - :param content: - :return: - """ - from django.template.defaultfilters import truncatechars_html - from djangoblog.utils import get_blog_setting - blogsetting = get_blog_setting() - return truncatechars_html(content, blogsetting.article_sub_length) - - -@register.filter(is_safe=True) -@stringfilter -def truncate(content): - from django.utils.html import strip_tags - - return strip_tags(content)[:150] - - -@register.inclusion_tag('blog/tags/breadcrumb.html') -def load_breadcrumb(article): - """ - 获得文章面包屑 - :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 { - 'names': names, - 'title': article.title, - 'count': len(names) + 1 - } - - -@register.inclusion_tag('blog/tags/article_tag_list.html') -def load_articletags(article): - """ - 文章标签 - :param article: - :return: - """ - tags = article.tags.all() - tags_list = [] - for tag in tags: - url = tag.get_absolute_url() - count = tag.get_article_count() - tags_list.append(( - url, count, tag, random.choice(settings.BOOTSTRAP_COLOR_TYPES) - )) - return { - 'article_tags_list': tags_list - } - - -@register.inclusion_tag('blog/tags/sidebar.html') -def load_sidebar(user, linktype): - """ - 加载侧边栏 - :return: - """ - value = cache.get("sidebar" + linktype) - if value: - value['user'] = user - return value - else: - 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] - # 标签云 计算字体大小 - # 根据总数计算出平均值 大小为 (数目/平均值)*步长 - increment = 5 - tags = Tag.objects.all() - sidebar_tags = None - if tags and len(tags) > 0: - s = [t for t in [(t, t.get_article_count()) for t in tags] if t[1]] - count = sum([t[1] for t in s]) - dd = 1 if (count == 0 or not len(tags)) else count / len(tags) - import random - sidebar_tags = list( - map(lambda x: (x[0], x[1], (x[1] / dd) * increment + 10), s)) - random.shuffle(sidebar_tags) - - value = { - 'recent_articles': recent_articles, - 'sidebar_categorys': sidebar_categorys, - 'most_read_articles': most_read_articles, - 'article_dates': dates, - 'sidebar_comments': commment_list, - 'sidabar_links': links, - 'show_google_adsense': blogsetting.show_google_adsense, - 'google_adsense_codes': blogsetting.google_adsense_codes, - 'open_site_comment': blogsetting.open_site_comment, - 'show_gongan_code': blogsetting.show_gongan_code, - 'sidebar_tags': sidebar_tags, - 'extra_sidebars': extra_sidebars - } - cache.set("sidebar" + linktype, value, 60 * 60 * 60 * 3) - logger.info('set sidebar cache.key:{key}'.format(key="sidebar" + linktype)) - value['user'] = user - return value - - -@register.inclusion_tag('blog/tags/article_meta_info.html') -def load_article_metas(article, user): - """ - 获得文章meta信息 - :param article: - :return: - """ - return { - 'article': article, - 'user': user - } - - -@register.inclusion_tag('blog/tags/article_pagination.html') -def load_pagination_info(page_obj, page_type, tag_name): - previous_url = '' - next_url = '' - if page_type == '': - if page_obj.has_next(): - next_number = page_obj.next_page_number() - next_url = reverse('blog:index_page', kwargs={'page': next_number}) - if page_obj.has_previous(): - previous_number = page_obj.previous_page_number() - previous_url = reverse( - 'blog:index_page', kwargs={ - 'page': previous_number}) - if page_type == '分类标签归档': - tag = get_object_or_404(Tag, name=tag_name) - if page_obj.has_next(): - next_number = page_obj.next_page_number() - next_url = reverse( - 'blog:tag_detail_page', - kwargs={ - 'page': next_number, - 'tag_name': tag.slug}) - if page_obj.has_previous(): - previous_number = page_obj.previous_page_number() - previous_url = reverse( - 'blog:tag_detail_page', - kwargs={ - 'page': previous_number, - 'tag_name': tag.slug}) - if page_type == '作者文章归档': - if page_obj.has_next(): - next_number = page_obj.next_page_number() - next_url = reverse( - 'blog:author_detail_page', - kwargs={ - 'page': next_number, - 'author_name': tag_name}) - if page_obj.has_previous(): - previous_number = page_obj.previous_page_number() - previous_url = reverse( - 'blog:author_detail_page', - kwargs={ - 'page': previous_number, - 'author_name': tag_name}) - - if page_type == '分类目录归档': - category = get_object_or_404(Category, name=tag_name) - if page_obj.has_next(): - next_number = page_obj.next_page_number() - next_url = reverse( - 'blog:category_detail_page', - kwargs={ - 'page': next_number, - 'category_name': category.slug}) - if page_obj.has_previous(): - previous_number = page_obj.previous_page_number() - previous_url = reverse( - 'blog:category_detail_page', - kwargs={ - 'page': previous_number, - 'category_name': category.slug}) - - return { - 'previous_url': previous_url, - 'next_url': next_url, - 'page_obj': page_obj - } - - -@register.inclusion_tag('blog/tags/article_info.html') -def load_article_detail(article, isindex, user): - """ - 加载文章详情 - :param article: - :param isindex:是否列表页,若是列表页只显示摘要 - :return: - """ - from djangoblog.utils import get_blog_setting - blogsetting = get_blog_setting() - - return { - 'article': article, - 'isindex': isindex, - 'user': user, - 'open_site_comment': blogsetting.open_site_comment, - } - - -# 返回用户头像URL -# 模板使用方法: {{ email|gravatar_url:150 }} -@register.filter -def gravatar_url(email, size=40): - """获得用户头像 - 优先使用OAuth头像,否则使用默认头像""" - cachekey = 'avatar/' + email - 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 - - -@register.filter -def gravatar(email, size=40): - """获得用户头像HTML标签""" - url = gravatar_url(email, size) - return mark_safe( - '用户头像' % - (url, size, size)) - - -@register.simple_tag -def query(qs, **kwargs): - """ template tag which allows queryset filtering. Usage: - {% query books author=author as mybooks %} - {% for book in mybooks %} - ... - {% 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 diff --git a/src/DjangoBlog/blog/tests.py b/src/DjangoBlog/blog/tests.py deleted file mode 100644 index ee13505..0000000 --- a/src/DjangoBlog/blog/tests.py +++ /dev/null @@ -1,232 +0,0 @@ -import os - -from django.conf import settings -from django.core.files.uploadedfile import SimpleUploadedFile -from django.core.management import call_command -from django.core.paginator import Paginator -from django.templatetags.static import static -from django.test import Client, RequestFactory, TestCase -from django.urls import reverse -from django.utils import timezone - -from accounts.models import BlogUser -from blog.forms import BlogSearchForm -from blog.models import Article, Category, Tag, SideBar, Links -from blog.templatetags.blog_tags import load_pagination_info, load_articletags -from djangoblog.utils import get_current_site, get_sha256 -from oauth.models import OAuthUser, OAuthConfig - - -# Create your tests here. - -class ArticleTest(TestCase): - def setUp(self): - self.client = Client() - self.factory = RequestFactory() - - def test_validate_article(self): - site = get_current_site().domain - user = BlogUser.objects.get_or_create( - email="liangliangyy@gmail.com", - username="liangliangyy")[0] - user.set_password("liangliangyy") - user.is_staff = True - user.is_superuser = True - user.save() - response = self.client.get(user.get_absolute_url()) - self.assertEqual(response.status_code, 200) - response = self.client.get('/admin/servermanager/emailsendlog/') - response = self.client.get('admin/admin/logentry/') - s = SideBar() - s.sequence = 1 - s.name = 'test' - s.content = 'test content' - s.is_enable = True - s.save() - - category = Category() - category.name = "category" - category.creation_time = timezone.now() - category.last_mod_time = timezone.now() - category.save() - - tag = Tag() - tag.name = "nicetag" - tag.save() - - article = Article() - article.title = "nicetitle" - article.body = "nicecontent" - article.author = user - article.category = category - article.type = 'a' - article.status = 'p' - - article.save() - self.assertEqual(0, article.tags.count()) - article.tags.add(tag) - article.save() - self.assertEqual(1, article.tags.count()) - - for i in range(20): - article = Article() - article.title = "nicetitle" + str(i) - article.body = "nicetitle" + str(i) - article.author = user - article.category = category - article.type = 'a' - article.status = 'p' - article.save() - article.tags.add(tag) - article.save() - from blog.documents import ELASTICSEARCH_ENABLED - if ELASTICSEARCH_ENABLED: - call_command("build_index") - response = self.client.get('/search', {'q': 'nicetitle'}) - self.assertEqual(response.status_code, 200) - - response = self.client.get(article.get_absolute_url()) - self.assertEqual(response.status_code, 200) - from djangoblog.spider_notify import SpiderNotify - SpiderNotify.notify(article.get_absolute_url()) - response = self.client.get(tag.get_absolute_url()) - self.assertEqual(response.status_code, 200) - - response = self.client.get(category.get_absolute_url()) - self.assertEqual(response.status_code, 200) - - response = self.client.get('/search', {'q': 'django'}) - self.assertEqual(response.status_code, 200) - s = load_articletags(article) - self.assertIsNotNone(s) - - self.client.login(username='liangliangyy', password='liangliangyy') - - response = self.client.get(reverse('blog:archives')) - self.assertEqual(response.status_code, 200) - - p = Paginator(Article.objects.all(), settings.PAGINATE_BY) - self.check_pagination(p, '', '') - - p = Paginator(Article.objects.filter(tags=tag), settings.PAGINATE_BY) - self.check_pagination(p, '分类标签归档', tag.slug) - - p = Paginator( - Article.objects.filter( - author__username='liangliangyy'), settings.PAGINATE_BY) - self.check_pagination(p, '作者文章归档', 'liangliangyy') - - p = Paginator(Article.objects.filter(category=category), settings.PAGINATE_BY) - self.check_pagination(p, '分类目录归档', category.slug) - - f = BlogSearchForm() - f.search() - # self.client.login(username='liangliangyy', password='liangliangyy') - from djangoblog.spider_notify import SpiderNotify - SpiderNotify.baidu_notify([article.get_full_url()]) - - from blog.templatetags.blog_tags import gravatar_url, gravatar - u = gravatar_url('liangliangyy@gmail.com') - u = gravatar('liangliangyy@gmail.com') - - link = Links( - sequence=1, - name="lylinux", - link='https://wwww.lylinux.net') - link.save() - response = self.client.get('/links.html') - self.assertEqual(response.status_code, 200) - - response = self.client.get('/feed/') - self.assertEqual(response.status_code, 200) - - response = self.client.get('/sitemap.xml') - self.assertEqual(response.status_code, 200) - - self.client.get("/admin/blog/article/1/delete/") - self.client.get('/admin/servermanager/emailsendlog/') - self.client.get('/admin/admin/logentry/') - self.client.get('/admin/admin/logentry/1/change/') - - def check_pagination(self, p, type, value): - for page in range(1, p.num_pages + 1): - s = load_pagination_info(p.page(page), type, value) - self.assertIsNotNone(s) - if s['previous_url']: - response = self.client.get(s['previous_url']) - self.assertEqual(response.status_code, 200) - if s['next_url']: - response = self.client.get(s['next_url']) - self.assertEqual(response.status_code, 200) - - def test_image(self): - import requests - rsp = requests.get( - 'https://www.python.org/static/img/python-logo.png') - imagepath = os.path.join(settings.BASE_DIR, 'python.png') - with open(imagepath, 'wb') as file: - file.write(rsp.content) - rsp = self.client.post('/upload') - self.assertEqual(rsp.status_code, 403) - sign = get_sha256(get_sha256(settings.SECRET_KEY)) - with open(imagepath, 'rb') as file: - imgfile = SimpleUploadedFile( - 'python.png', file.read(), content_type='image/jpg') - form_data = {'python.png': imgfile} - rsp = self.client.post( - '/upload?sign=' + sign, form_data, follow=True) - self.assertEqual(rsp.status_code, 200) - os.remove(imagepath) - from djangoblog.utils import save_user_avatar, send_email - send_email(['qq@qq.com'], 'testTitle', 'testContent') - save_user_avatar( - 'https://www.python.org/static/img/python-logo.png') - - def test_errorpage(self): - rsp = self.client.get('/eee') - self.assertEqual(rsp.status_code, 404) - - def test_commands(self): - user = BlogUser.objects.get_or_create( - email="liangliangyy@gmail.com", - username="liangliangyy")[0] - user.set_password("liangliangyy") - user.is_staff = True - user.is_superuser = True - user.save() - - c = OAuthConfig() - c.type = 'qq' - c.appkey = 'appkey' - c.appsecret = 'appsecret' - c.save() - - u = OAuthUser() - u.type = 'qq' - u.openid = 'openid' - u.user = user - u.picture = static("/blog/img/avatar.png") - u.metadata = ''' -{ -"figureurl": "https://qzapp.qlogo.cn/qzapp/101513904/C740E30B4113EAA80E0D9918ABC78E82/30" -}''' - u.save() - - u = OAuthUser() - u.type = 'qq' - u.openid = 'openid1' - u.picture = 'https://qzapp.qlogo.cn/qzapp/101513904/C740E30B4113EAA80E0D9918ABC78E82/30' - u.metadata = ''' - { - "figureurl": "https://qzapp.qlogo.cn/qzapp/101513904/C740E30B4113EAA80E0D9918ABC78E82/30" - }''' - u.save() - - from blog.documents import ELASTICSEARCH_ENABLED - if ELASTICSEARCH_ENABLED: - call_command("build_index") - call_command("ping_baidu", "all") - call_command("create_testdata") - call_command("clear_cache") - call_command("sync_user_avatar") - call_command("build_search_words") diff --git a/src/DjangoBlog/blog/urls.py b/src/DjangoBlog/blog/urls.py deleted file mode 100644 index adf2703..0000000 --- a/src/DjangoBlog/blog/urls.py +++ /dev/null @@ -1,62 +0,0 @@ -from django.urls import path -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'), - 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'), - path( - 'archives.html', - cache_page( - 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, - name='clean'), -] diff --git a/src/DjangoBlog/blog/views.py b/src/DjangoBlog/blog/views.py deleted file mode 100644 index 773bb75..0000000 --- a/src/DjangoBlog/blog/views.py +++ /dev/null @@ -1,380 +0,0 @@ -import logging -import os -import uuid - -from django.conf import settings -from django.core.paginator import Paginator -from django.http import HttpResponse, HttpResponseForbidden -from django.shortcuts import get_object_or_404 -from django.shortcuts import render -from django.templatetags.static import static -from django.utils import timezone -from django.utils.translation import gettext_lazy as _ -from django.views.decorators.csrf import csrf_exempt -from django.views.generic.detail import DetailView -from django.views.generic.list import ListView -from haystack.views import SearchView - -from blog.models import Article, Category, LinkShowType, Links, Tag -from comments.forms import CommentForm -from djangoblog.plugin_manage import hooks -from djangoblog.plugin_manage.hook_constants import ARTICLE_CONTENT_HOOK_NAME -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' - - # context_object_name属性用于给上下文变量取名(在模板中使用该名字) - context_object_name = 'article_list' - - # 页面类型,分类目录或标签列表等 - page_type = '' - paginate_by = settings.PAGINATE_BY - page_kwarg = 'page' - link_type = LinkShowType.L - - def get_view_cache_key(self): - return self.request.get['pages'] - - @property - def page_number(self): - page_kwarg = self.page_kwarg - page = self.kwargs.get( - page_kwarg) or self.request.GET.get(page_kwarg) or 1 - return page - - def get_queryset_cache_key(self): - """ - 子类重写.获得queryset的缓存key - """ - raise NotImplementedError() - - def get_queryset_data(self): - """ - 子类重写.获取queryset的数据 - """ - raise NotImplementedError() - - def get_queryset_from_cache(self, cache_key): - ''' - 缓存页面数据 - :param cache_key: 缓存key - :return: - ''' - value = cache.get(cache_key) - if value: - logger.info('get view cache.key:{key}'.format(key=cache_key)) - return value - else: - article_list = self.get_queryset_data() - cache.set(cache_key, article_list) - logger.info('set view cache.key:{key}'.format(key=cache_key)) - return article_list - - def get_queryset(self): - ''' - 重写默认,从缓存获取数据 - :return: - ''' - key = self.get_queryset_cache_key() - value = self.get_queryset_from_cache(key) - return value - - def get_context_data(self, **kwargs): - kwargs['linktype'] = self.link_type - return super(ArticleListView, self).get_context_data(**kwargs) - - -class IndexView(ArticleListView): - ''' - 首页 - ''' - # 友情链接类型 - link_type = LinkShowType.I - - def get_queryset_data(self): - article_list = Article.objects.filter(type='a', status='p') - return article_list - - def get_queryset_cache_key(self): - cache_key = 'index_{page}'.format(page=self.page_number) - return cache_key - - -class ArticleDetailView(DetailView): - ''' - 文章详情页面 - ''' - template_name = 'blog/article_detail.html' - model = Article - pk_url_kwarg = 'article_id' - context_object_name = "article" - - def get_context_data(self, **kwargs): - comment_form = CommentForm() - - article_comments = self.object.comment_list() - parent_comments = article_comments.filter(parent_comment=None) - blog_setting = get_blog_setting() - paginator = Paginator(parent_comments, blog_setting.article_comment_count) - page = self.request.GET.get('comment_page', '1') - if not page.isnumeric(): - page = 1 - else: - page = int(page) - if page < 1: - page = 1 - if page > paginator.num_pages: - page = paginator.num_pages - - p_comments = paginator.page(page) - next_page = p_comments.next_page_number() if p_comments.has_next() else None - prev_page = p_comments.previous_page_number() if p_comments.has_previous() else None - - if next_page: - kwargs[ - 'comment_next_page_url'] = self.object.get_absolute_url() + f'?comment_page={next_page}#commentlist-container' - if prev_page: - kwargs[ - 'comment_prev_page_url'] = self.object.get_absolute_url() + f'?comment_page={prev_page}#commentlist-container' - kwargs['form'] = comment_form - kwargs['article_comments'] = article_comments - kwargs['p_comments'] = p_comments - kwargs['comment_count'] = len( - article_comments) if article_comments else 0 - - kwargs['next_article'] = self.object.next_article - kwargs['prev_article'] = self.object.prev_article - - 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) - return context - - -class CategoryDetailView(ArticleListView): - ''' - 分类目录列表 - ''' - page_type = "分类目录归档" - - def get_queryset_data(self): - slug = self.kwargs['category_name'] - category = get_object_or_404(Category, slug=slug) - - categoryname = category.name - self.categoryname = categoryname - categorynames = list( - map(lambda c: c.name, category.get_sub_categorys())) - article_list = Article.objects.filter( - category__name__in=categorynames, status='p') - return article_list - - def get_queryset_cache_key(self): - slug = self.kwargs['category_name'] - category = get_object_or_404(Category, slug=slug) - categoryname = category.name - self.categoryname = categoryname - cache_key = 'category_list_{categoryname}_{page}'.format( - categoryname=categoryname, page=self.page_number) - return cache_key - - def get_context_data(self, **kwargs): - - categoryname = self.categoryname - try: - categoryname = categoryname.split('/')[-1] - except BaseException: - pass - kwargs['page_type'] = CategoryDetailView.page_type - kwargs['tag_name'] = categoryname - return super(CategoryDetailView, self).get_context_data(**kwargs) - - -class AuthorDetailView(ArticleListView): - ''' - 作者详情页 - ''' - page_type = '作者文章归档' - - def get_queryset_cache_key(self): - from uuslug import slugify - author_name = slugify(self.kwargs['author_name']) - cache_key = 'author_{author_name}_{page}'.format( - author_name=author_name, page=self.page_number) - return cache_key - - def get_queryset_data(self): - author_name = self.kwargs['author_name'] - article_list = Article.objects.filter( - author__username=author_name, type='a', status='p') - return article_list - - def get_context_data(self, **kwargs): - author_name = self.kwargs['author_name'] - kwargs['page_type'] = AuthorDetailView.page_type - kwargs['tag_name'] = author_name - return super(AuthorDetailView, self).get_context_data(**kwargs) - - -class TagDetailView(ArticleListView): - ''' - 标签列表页面 - ''' - page_type = '分类标签归档' - - def get_queryset_data(self): - slug = self.kwargs['tag_name'] - tag = get_object_or_404(Tag, slug=slug) - tag_name = tag.name - self.name = tag_name - article_list = Article.objects.filter( - tags__name=tag_name, type='a', status='p') - return article_list - - def get_queryset_cache_key(self): - slug = self.kwargs['tag_name'] - tag = get_object_or_404(Tag, slug=slug) - tag_name = tag.name - self.name = tag_name - cache_key = 'tag_{tag_name}_{page}'.format( - tag_name=tag_name, page=self.page_number) - return cache_key - - def get_context_data(self, **kwargs): - # tag_name = self.kwargs['tag_name'] - tag_name = self.name - kwargs['page_type'] = TagDetailView.page_type - kwargs['tag_name'] = tag_name - return super(TagDetailView, self).get_context_data(**kwargs) - - -class ArchivesView(ArticleListView): - ''' - 文章归档页面 - ''' - page_type = '文章归档' - paginate_by = None - page_kwarg = None - template_name = 'blog/article_archives.html' - - def get_queryset_data(self): - return Article.objects.filter(status='p').all() - - def get_queryset_cache_key(self): - cache_key = 'archives' - return cache_key - - -class LinkListView(ListView): - model = Links - template_name = 'blog/links_list.html' - - def get_queryset(self): - return Links.objects.filter(is_enable=True) - - -class EsSearchView(SearchView): - def get_context(self): - paginator, page = self.build_page() - context = { - "query": self.query, - "form": self.form, - "page": page, - "paginator": paginator, - "suggestion": None, - } - if hasattr(self.results, "query") and self.results.query.backend.include_spelling: - context["suggestion"] = self.results.query.get_spelling_suggestion() - context.update(self.extra_context()) - - return context - - -@csrf_exempt -def fileupload(request): - """ - 该方法需自己写调用端来上传图片,该方法仅提供图床功能 - :param request: - :return: - """ - if request.method == 'POST': - sign = request.GET.get('sign', None) - if not sign: - return HttpResponseForbidden() - if not sign == get_sha256(get_sha256(settings.SECRET_KEY)): - return HttpResponseForbidden() - response = [] - for filename in request.FILES: - timestr = timezone.now().strftime('%Y/%m/%d') - imgextensions = ['jpg', 'png', 'jpeg', 'bmp'] - fname = u''.join(str(filename)) - isimage = len([i for i in imgextensions if fname.find(i) >= 0]) > 0 - base_dir = os.path.join(settings.STATICFILES, "files" if not isimage else "image", timestr) - if not os.path.exists(base_dir): - os.makedirs(base_dir) - savepath = os.path.normpath(os.path.join(base_dir, f"{uuid.uuid4().hex}{os.path.splitext(filename)[-1]}")) - if not savepath.startswith(base_dir): - return HttpResponse("only for post") - with open(savepath, 'wb+') as wfile: - for chunk in request.FILES[filename].chunks(): - wfile.write(chunk) - if isimage: - from PIL import Image - image = Image.open(savepath) - image.save(savepath, quality=20, optimize=True) - url = static(savepath) - response.append(url) - return HttpResponse(response) - - else: - return HttpResponse("only for post") - - -def page_not_found_view( - request, - exception, - template_name='blog/error_page.html'): - if exception: - logger.error(exception) - url = request.get_full_path() - return render(request, - template_name, - {'message': _('Sorry, the page you requested is not found, please click the home page to see other?'), - 'statuscode': '404'}, - status=404) - - -def server_error_view(request, template_name='blog/error_page.html'): - return render(request, - template_name, - {'message': _('Sorry, the server is busy, please click the home page to see other?'), - 'statuscode': '500'}, - status=500) - - -def permission_denied_view( - request, - exception, - template_name='blog/error_page.html'): - if exception: - logger.error(exception) - return render( - request, template_name, { - 'message': _('Sorry, you do not have permission to access this page?'), - 'statuscode': '403'}, status=403) - - -def clean_cache_view(request): - cache.clear() - return HttpResponse('ok') diff --git a/src/DjangoBlog/codecov.yml b/src/DjangoBlog/codecov.yml deleted file mode 100644 index 2298829..0000000 --- a/src/DjangoBlog/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/DjangoBlog/comments/__init__.py b/src/DjangoBlog/comments/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/src/DjangoBlog/comments/admin.py b/src/DjangoBlog/comments/admin.py deleted file mode 100644 index dbde14f..0000000 --- a/src/DjangoBlog/comments/admin.py +++ /dev/null @@ -1,49 +0,0 @@ -from django.contrib import admin -from django.urls import reverse -from django.utils.html import format_html -from django.utils.translation import gettext_lazy as _ - - -def disable_commentstatus(modeladmin, request, queryset): - queryset.update(is_enable=False) - - -def enable_commentstatus(modeladmin, request, queryset): - queryset.update(is_enable=True) - - -disable_commentstatus.short_description = _('Disable comments') -enable_commentstatus.short_description = _('Enable comments') - - -class CommentAdmin(admin.ModelAdmin): - list_per_page = 20 - list_display = ( - 'id', - 'body', - 'link_to_userinfo', - 'link_to_article', - 'is_enable', - 'creation_time') - list_display_links = ('id', 'body', 'is_enable') - list_filter = ('is_enable',) - exclude = ('creation_time', 'last_modify_time') - actions = [disable_commentstatus, enable_commentstatus] - raw_id_fields = ('author', 'article') - search_fields = ('body',) - - def link_to_userinfo(self, obj): - info = (obj.author._meta.app_label, obj.author._meta.model_name) - link = reverse('admin:%s_%s_change' % info, args=(obj.author.id,)) - return format_html( - u'%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/DjangoBlog/comments/apps.py b/src/DjangoBlog/comments/apps.py deleted file mode 100644 index ff01b77..0000000 --- a/src/DjangoBlog/comments/apps.py +++ /dev/null @@ -1,5 +0,0 @@ -from django.apps import AppConfig - - -class CommentsConfig(AppConfig): - name = 'comments' diff --git a/src/DjangoBlog/comments/forms.py b/src/DjangoBlog/comments/forms.py deleted file mode 100644 index e83737d..0000000 --- a/src/DjangoBlog/comments/forms.py +++ /dev/null @@ -1,13 +0,0 @@ -from django import forms -from django.forms import ModelForm - -from .models import Comment - - -class CommentForm(ModelForm): - parent_comment_id = forms.IntegerField( - widget=forms.HiddenInput, required=False) - - class Meta: - model = Comment - fields = ['body'] diff --git a/src/DjangoBlog/comments/migrations/0001_initial.py b/src/DjangoBlog/comments/migrations/0001_initial.py deleted file mode 100644 index 61d1e53..0000000 --- a/src/DjangoBlog/comments/migrations/0001_initial.py +++ /dev/null @@ -1,38 +0,0 @@ -# Generated by Django 4.1.7 on 2023-03-02 07:14 - -from django.conf import settings -from django.db import migrations, models -import django.db.models.deletion -import django.utils.timezone - - -class Migration(migrations.Migration): - - initial = True - - dependencies = [ - ('blog', '0001_initial'), - migrations.swappable_dependency(settings.AUTH_USER_MODEL), - ] - - operations = [ - migrations.CreateModel( - name='Comment', - fields=[ - ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('body', models.TextField(max_length=300, verbose_name='正文')), - ('created_time', models.DateTimeField(default=django.utils.timezone.now, verbose_name='创建时间')), - ('last_mod_time', models.DateTimeField(default=django.utils.timezone.now, verbose_name='修改时间')), - ('is_enable', models.BooleanField(default=True, verbose_name='是否显示')), - ('article', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='blog.article', verbose_name='文章')), - ('author', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL, verbose_name='作者')), - ('parent_comment', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='comments.comment', verbose_name='上级评论')), - ], - options={ - 'verbose_name': '评论', - 'verbose_name_plural': '评论', - 'ordering': ['-id'], - 'get_latest_by': 'id', - }, - ), - ] diff --git a/src/DjangoBlog/comments/migrations/0002_alter_comment_is_enable.py b/src/DjangoBlog/comments/migrations/0002_alter_comment_is_enable.py deleted file mode 100644 index 17c44db..0000000 --- a/src/DjangoBlog/comments/migrations/0002_alter_comment_is_enable.py +++ /dev/null @@ -1,18 +0,0 @@ -# Generated by Django 4.1.7 on 2023-04-24 13:48 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('comments', '0001_initial'), - ] - - operations = [ - migrations.AlterField( - model_name='comment', - name='is_enable', - field=models.BooleanField(default=False, verbose_name='是否显示'), - ), - ] diff --git a/src/DjangoBlog/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 deleted file mode 100644 index a1ca970..0000000 --- a/src/DjangoBlog/comments/migrations/0003_alter_comment_options_remove_comment_created_time_and_more.py +++ /dev/null @@ -1,60 +0,0 @@ -# Generated by Django 4.2.5 on 2023-09-06 13:13 - -from django.conf import settings -from django.db import migrations, models -import django.db.models.deletion -import django.utils.timezone - - -class Migration(migrations.Migration): - - dependencies = [ - migrations.swappable_dependency(settings.AUTH_USER_MODEL), - ('blog', '0005_alter_article_options_alter_category_options_and_more'), - ('comments', '0002_alter_comment_is_enable'), - ] - - operations = [ - migrations.AlterModelOptions( - name='comment', - options={'get_latest_by': 'id', 'ordering': ['-id'], 'verbose_name': 'comment', 'verbose_name_plural': 'comment'}, - ), - migrations.RemoveField( - model_name='comment', - name='created_time', - ), - migrations.RemoveField( - model_name='comment', - name='last_mod_time', - ), - migrations.AddField( - model_name='comment', - name='creation_time', - field=models.DateTimeField(default=django.utils.timezone.now, verbose_name='creation time'), - ), - migrations.AddField( - model_name='comment', - name='last_modify_time', - field=models.DateTimeField(default=django.utils.timezone.now, verbose_name='last modify time'), - ), - migrations.AlterField( - model_name='comment', - name='article', - field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='blog.article', verbose_name='article'), - ), - migrations.AlterField( - model_name='comment', - name='author', - field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL, verbose_name='author'), - ), - migrations.AlterField( - model_name='comment', - name='is_enable', - field=models.BooleanField(default=False, verbose_name='enable'), - ), - migrations.AlterField( - model_name='comment', - name='parent_comment', - field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='comments.comment', verbose_name='parent comment'), - ), - ] diff --git a/src/DjangoBlog/comments/migrations/__init__.py b/src/DjangoBlog/comments/migrations/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/src/DjangoBlog/comments/models.py b/src/DjangoBlog/comments/models.py deleted file mode 100644 index 7c3bbc8..0000000 --- a/src/DjangoBlog/comments/models.py +++ /dev/null @@ -1,39 +0,0 @@ -from django.conf import settings -from django.db import models -from django.utils.timezone import now -from django.utils.translation import gettext_lazy as _ - -from blog.models import Article - - -# Create your models here. - -class Comment(models.Model): - body = models.TextField('正文', max_length=300) - creation_time = models.DateTimeField(_('creation time'), default=now) - last_modify_time = models.DateTimeField(_('last modify time'), default=now) - author = models.ForeignKey( - settings.AUTH_USER_MODEL, - verbose_name=_('author'), - on_delete=models.CASCADE) - article = models.ForeignKey( - Article, - verbose_name=_('article'), - on_delete=models.CASCADE) - parent_comment = models.ForeignKey( - 'self', - verbose_name=_('parent comment'), - blank=True, - null=True, - on_delete=models.CASCADE) - is_enable = models.BooleanField(_('enable'), - default=False, blank=False, null=False) - - class Meta: - ordering = ['-id'] - verbose_name = _('comment') - verbose_name_plural = verbose_name - get_latest_by = 'id' - - def __str__(self): - return self.body diff --git a/src/DjangoBlog/comments/templatetags/__init__.py b/src/DjangoBlog/comments/templatetags/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/src/DjangoBlog/comments/templatetags/comments_tags.py b/src/DjangoBlog/comments/templatetags/comments_tags.py deleted file mode 100644 index fde02b4..0000000 --- a/src/DjangoBlog/comments/templatetags/comments_tags.py +++ /dev/null @@ -1,30 +0,0 @@ -from django import template - -register = template.Library() - - -@register.simple_tag -def parse_commenttree(commentlist, comment): - """获得当前评论子评论的列表 - 用法: {% parse_commenttree article_comments comment as childcomments %} - """ - datas = [] - - def parse(c): - childs = commentlist.filter(parent_comment=c, is_enable=True) - for child in childs: - datas.append(child) - parse(child) - - parse(comment) - return datas - - -@register.inclusion_tag('comments/tags/comment_item.html') -def show_comment_item(comment, ischild): - """评论""" - depth = 1 if ischild else 2 - return { - 'comment_item': comment, - 'depth': depth - } diff --git a/src/DjangoBlog/comments/tests.py b/src/DjangoBlog/comments/tests.py deleted file mode 100644 index 2a7f55f..0000000 --- a/src/DjangoBlog/comments/tests.py +++ /dev/null @@ -1,109 +0,0 @@ -from django.test import Client, RequestFactory, TransactionTestCase -from django.urls import reverse - -from accounts.models import BlogUser -from blog.models import Category, Article -from comments.models import Comment -from comments.templatetags.comments_tags import * -from djangoblog.utils import get_max_articleid_commentid - - -# Create your tests here. - -class CommentsTest(TransactionTestCase): - def setUp(self): - self.client = Client() - self.factory = RequestFactory() - from blog.models import BlogSettings - value = BlogSettings() - value.comment_need_review = True - value.save() - - self.user = BlogUser.objects.create_superuser( - email="liangliangyy1@gmail.com", - username="liangliangyy1", - password="liangliangyy1") - - def update_article_comment_status(self, article): - comments = article.comment_set.all() - for comment in comments: - comment.is_enable = True - comment.save() - - def test_validate_comment(self): - self.client.login(username='liangliangyy1', password='liangliangyy1') - - category = Category() - category.name = "categoryccc" - category.save() - - article = Article() - article.title = "nicetitleccc" - article.body = "nicecontentccc" - article.author = self.user - article.category = category - article.type = 'a' - article.status = 'p' - article.save() - - comment_url = reverse( - 'comments:postcomment', kwargs={ - 'article_id': article.id}) - - response = self.client.post(comment_url, - { - 'body': '123ffffffffff' - }) - - self.assertEqual(response.status_code, 302) - - article = Article.objects.get(pk=article.pk) - self.assertEqual(len(article.comment_list()), 0) - self.update_article_comment_status(article) - - self.assertEqual(len(article.comment_list()), 1) - - response = self.client.post(comment_url, - { - 'body': '123ffffffffff', - }) - - self.assertEqual(response.status_code, 302) - - article = Article.objects.get(pk=article.pk) - self.update_article_comment_status(article) - self.assertEqual(len(article.comment_list()), 2) - parent_comment_id = article.comment_list()[0].id - - response = self.client.post(comment_url, - { - 'body': ''' - # Title1 - - ```python - import os - ``` - - [url](https://www.lylinux.net/) - - [ddd](http://www.baidu.com) - - - ''', - 'parent_comment_id': parent_comment_id - }) - - self.assertEqual(response.status_code, 302) - self.update_article_comment_status(article) - article = Article.objects.get(pk=article.pk) - self.assertEqual(len(article.comment_list()), 3) - comment = Comment.objects.get(id=parent_comment_id) - tree = parse_commenttree(article.comment_list(), comment) - self.assertEqual(len(tree), 1) - data = show_comment_item(comment, True) - self.assertIsNotNone(data) - s = get_max_articleid_commentid() - self.assertIsNotNone(s) - - from comments.utils import send_comment_email - send_comment_email(comment) diff --git a/src/DjangoBlog/comments/urls.py b/src/DjangoBlog/comments/urls.py deleted file mode 100644 index 7df3fab..0000000 --- a/src/DjangoBlog/comments/urls.py +++ /dev/null @@ -1,11 +0,0 @@ -from django.urls import path - -from . import views - -app_name = "comments" -urlpatterns = [ - path( - 'article//postcomment', - views.CommentPostView.as_view(), - name='postcomment'), -] diff --git a/src/DjangoBlog/comments/utils.py b/src/DjangoBlog/comments/utils.py deleted file mode 100644 index f01dba7..0000000 --- a/src/DjangoBlog/comments/utils.py +++ /dev/null @@ -1,38 +0,0 @@ -import logging - -from django.utils.translation import gettext_lazy as _ - -from djangoblog.utils import get_current_site -from djangoblog.utils import send_email - -logger = logging.getLogger(__name__) - - -def send_comment_email(comment): - site = get_current_site().domain - subject = _('Thanks for your comment') - article_url = f"https://{site}{comment.article.get_absolute_url()}" - html_content = _("""

Thank you very much for your comments on this site

- You can visit %(article_title)s - to review your comments, - Thank you again! -
- 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 -
- go check it out! -
- If the link above cannot be opened, please copy this link to your browser. - %(article_url)s - """) % {'article_url': article_url, 'article_title': comment.article.title, - 'comment_body': comment.parent_comment.body} - tomail = comment.parent_comment.author.email - send_email([tomail], subject, html_content) - except Exception as e: - logger.error(e) diff --git a/src/DjangoBlog/comments/views.py b/src/DjangoBlog/comments/views.py deleted file mode 100644 index ad9b2b9..0000000 --- a/src/DjangoBlog/comments/views.py +++ /dev/null @@ -1,63 +0,0 @@ -# Create your views here. -from django.core.exceptions import ValidationError -from django.http import HttpResponseRedirect -from django.shortcuts import get_object_or_404 -from django.utils.decorators import method_decorator -from django.views.decorators.csrf import csrf_protect -from django.views.generic.edit import FormView - -from accounts.models import BlogUser -from blog.models import Article -from .forms import CommentForm -from .models import Comment - - -class CommentPostView(FormView): - form_class = CommentForm - template_name = 'blog/article_detail.html' - - @method_decorator(csrf_protect) - def dispatch(self, *args, **kwargs): - return super(CommentPostView, self).dispatch(*args, **kwargs) - - def get(self, request, *args, **kwargs): - article_id = self.kwargs['article_id'] - article = get_object_or_404(Article, pk=article_id) - url = article.get_absolute_url() - return HttpResponseRedirect(url + "#comments") - - def form_invalid(self, form): - article_id = self.kwargs['article_id'] - article = get_object_or_404(Article, pk=article_id) - - return self.render_to_response({ - 'form': form, - 'article': article - }) - - def form_valid(self, form): - """提交的数据验证合法后的逻辑""" - user = self.request.user - author = BlogUser.objects.get(pk=user.pk) - article_id = self.kwargs['article_id'] - article = get_object_or_404(Article, pk=article_id) - - if article.comment_status == 'c' or article.status == 'c': - raise ValidationError("该文章评论已关闭.") - comment = form.save(False) - comment.article = article - from djangoblog.utils import get_blog_setting - settings = get_blog_setting() - if not settings.comment_need_review: - comment.is_enable = True - comment.author = author - - if form.cleaned_data['parent_comment_id']: - parent_comment = Comment.objects.get( - pk=form.cleaned_data['parent_comment_id']) - comment.parent_comment = parent_comment - - comment.save(True) - return HttpResponseRedirect( - "%s#div-comment-%d" % - (article.get_absolute_url(), comment.pk)) diff --git a/src/DjangoBlog/deploy/docker-compose/docker-compose.es.yml b/src/DjangoBlog/deploy/docker-compose/docker-compose.es.yml deleted file mode 100644 index 83e35ff..0000000 --- a/src/DjangoBlog/deploy/docker-compose/docker-compose.es.yml +++ /dev/null @@ -1,48 +0,0 @@ -version: '3' - -services: - es: - image: liangliangyy/elasticsearch-analysis-ik:8.6.1 - container_name: es - restart: always - environment: - - discovery.type=single-node - - "ES_JAVA_OPTS=-Xms512m -Xmx512m" - ports: - - 9200:9200 - volumes: - - ./bin/datas/es/:/usr/share/elasticsearch/data/ - - kibana: - image: kibana:8.6.1 - restart: always - container_name: kibana - ports: - - 5601:5601 - environment: - - ELASTICSEARCH_HOSTS=http://es:9200 - - djangoblog: - build: . - restart: always - command: bash -c 'sh /code/djangoblog/bin/docker_start.sh' - ports: - - "8000:8000" - volumes: - - ./collectedstatic:/code/djangoblog/collectedstatic - - ./uploads:/code/djangoblog/uploads - environment: - - DJANGO_MYSQL_DATABASE=djangoblog - - DJANGO_MYSQL_USER=root - - DJANGO_MYSQL_PASSWORD=DjAnGoBlOg!2!Q@W#E - - DJANGO_MYSQL_HOST=db - - DJANGO_MYSQL_PORT=3306 - - DJANGO_MEMCACHED_LOCATION=memcached:11211 - - DJANGO_ELASTICSEARCH_HOST=es:9200 - links: - - db - - memcached - depends_on: - - db - container_name: djangoblog - diff --git a/src/DjangoBlog/deploy/docker-compose/docker-compose.yml b/src/DjangoBlog/deploy/docker-compose/docker-compose.yml deleted file mode 100644 index 9609af3..0000000 --- a/src/DjangoBlog/deploy/docker-compose/docker-compose.yml +++ /dev/null @@ -1,60 +0,0 @@ -version: '3' - -services: - db: - image: mysql:latest - restart: always - environment: - - MYSQL_DATABASE=djangoblog - - MYSQL_ROOT_PASSWORD=DjAnGoBlOg!2!Q@W#E - ports: - - 3306:3306 - volumes: - - ./bin/datas/mysql/:/var/lib/mysql - depends_on: - - redis - container_name: db - - djangoblog: - build: - context: ../../ - restart: always - command: bash -c 'sh /code/djangoblog/bin/docker_start.sh' - ports: - - "8000:8000" - volumes: - - ./collectedstatic:/code/djangoblog/collectedstatic - - ./logs:/code/djangoblog/logs - - ./uploads:/code/djangoblog/uploads - environment: - - DJANGO_MYSQL_DATABASE=djangoblog - - DJANGO_MYSQL_USER=root - - DJANGO_MYSQL_PASSWORD=DjAnGoBlOg!2!Q@W#E - - DJANGO_MYSQL_HOST=db - - DJANGO_MYSQL_PORT=3306 - - DJANGO_REDIS_URL=redis:6379 - links: - - db - - redis - depends_on: - - db - container_name: djangoblog - nginx: - restart: always - image: nginx:latest - ports: - - "80:80" - - "443:443" - volumes: - - ./bin/nginx.conf:/etc/nginx/nginx.conf - - ./collectedstatic:/code/djangoblog/collectedstatic - links: - - djangoblog:djangoblog - container_name: nginx - - redis: - restart: always - image: redis:latest - container_name: redis - ports: - - "6379:6379" diff --git a/src/DjangoBlog/deploy/entrypoint.sh b/src/DjangoBlog/deploy/entrypoint.sh deleted file mode 100644 index 2fb6491..0000000 --- a/src/DjangoBlog/deploy/entrypoint.sh +++ /dev/null @@ -1,31 +0,0 @@ -#!/usr/bin/env bash -NAME="djangoblog" -DJANGODIR=/code/djangoblog -USER=root -GROUP=root -NUM_WORKERS=1 -DJANGO_WSGI_MODULE=djangoblog.wsgi - - -echo "Starting $NAME as `whoami`" - -cd $DJANGODIR - -export PYTHONPATH=$DJANGODIR:$PYTHONPATH - -python manage.py makemigrations && \ - python manage.py migrate && \ - python manage.py collectstatic --noinput && \ - python manage.py compress --force && \ - python manage.py build_index && \ - python manage.py compilemessages || exit 1 - -exec gunicorn ${DJANGO_WSGI_MODULE}:application \ ---name $NAME \ ---workers $NUM_WORKERS \ ---user=$USER --group=$GROUP \ ---bind 0.0.0.0:8000 \ ---log-level=debug \ ---log-file=- \ ---worker-class gevent \ ---threads 4 diff --git a/src/DjangoBlog/deploy/k8s/configmap.yaml b/src/DjangoBlog/deploy/k8s/configmap.yaml deleted file mode 100644 index 835d4ad..0000000 --- a/src/DjangoBlog/deploy/k8s/configmap.yaml +++ /dev/null @@ -1,119 +0,0 @@ -apiVersion: v1 -kind: ConfigMap -metadata: - name: web-nginx-config - namespace: djangoblog -data: - nginx.conf: | - user nginx; - worker_processes auto; - error_log /var/log/nginx/error.log notice; - pid /var/run/nginx.pid; - - events { - worker_connections 1024; - multi_accept on; - use epoll; - } - - http { - include /etc/nginx/mime.types; - default_type application/octet-stream; - - log_format main '$remote_addr - $remote_user [$time_local] "$request" ' - '$status $body_bytes_sent "$http_referer" ' - '"$http_user_agent" "$http_x_forwarded_for"'; - - access_log /var/log/nginx/access.log main; - - sendfile on; - keepalive_timeout 65; - gzip on; - gzip_disable "msie6"; - - gzip_vary on; - gzip_proxied any; - gzip_comp_level 8; - gzip_buffers 16 8k; - gzip_http_version 1.1; - gzip_types text/plain application/javascript application/x-javascript text/css application/xml text/javascript application/x-httpd-php image/jpeg image/gif image/png; - - # Include server configurations - include /etc/nginx/conf.d/*.conf; - } - djangoblog.conf: | - server { - server_name lylinux.net; - root /code/djangoblog/collectedstatic/; - listen 80; - keepalive_timeout 70; - location /static/ { - expires max; - alias /code/djangoblog/collectedstatic/; - } - - location ~* (robots\.txt|ads\.txt|favicon\.ico|favion\.ico|crossdomain\.xml|google93fd32dbd906620a\.html|BingSiteAuth\.xml|baidu_verify_Ijeny6KrmS\.html)$ { - root /resource/djangopub; - expires 1d; - access_log off; - error_log off; - } - - location / { - proxy_set_header X-Real-IP $remote_addr; - proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; - proxy_set_header Host $http_host; - proxy_set_header X-NginX-Proxy true; - proxy_redirect off; - if (!-f $request_filename) { - proxy_pass http://djangoblog:8000; - break; - } - } - } - server { - server_name www.lylinux.net; - listen 80; - return 301 https://lylinux.net$request_uri; - } - resource.lylinux.net.conf: | - server { - index index.html index.htm; - server_name resource.lylinux.net; - root /resource/; - - location /djangoblog/ { - alias /code/djangoblog/collectedstatic/; - } - - access_log off; - error_log off; - include lylinux/resource.conf; - } - lylinux.resource.conf: | - expires max; - access_log off; - log_not_found off; - add_header Pragma public; - add_header Cache-Control "public"; - add_header "Access-Control-Allow-Origin" "*"; - ---- -apiVersion: v1 -kind: ConfigMap -metadata: - name: djangoblog-env - namespace: djangoblog -data: - DJANGO_MYSQL_DATABASE: djangoblog - DJANGO_MYSQL_USER: db_user - DJANGO_MYSQL_PASSWORD: db_password - DJANGO_MYSQL_HOST: db_host - DJANGO_MYSQL_PORT: db_port - DJANGO_REDIS_URL: "redis:6379" - DJANGO_DEBUG: "False" - MYSQL_ROOT_PASSWORD: db_password - MYSQL_DATABASE: djangoblog - MYSQL_PASSWORD: db_password - DJANGO_SECRET_KEY: xxxxxxxxxxxxxxxxxxxxxxxxxxxxx - diff --git a/src/DjangoBlog/deploy/k8s/deployment.yaml b/src/DjangoBlog/deploy/k8s/deployment.yaml deleted file mode 100644 index b50c411..0000000 --- a/src/DjangoBlog/deploy/k8s/deployment.yaml +++ /dev/null @@ -1,274 +0,0 @@ -apiVersion: apps/v1 -kind: Deployment -metadata: - name: djangoblog - namespace: djangoblog - labels: - app: djangoblog -spec: - replicas: 3 - selector: - matchLabels: - app: djangoblog - template: - metadata: - labels: - app: djangoblog - spec: - containers: - - name: djangoblog - image: liangliangyy/djangoblog:latest - imagePullPolicy: Always - ports: - - containerPort: 8000 - envFrom: - - configMapRef: - name: djangoblog-env - readinessProbe: - httpGet: - path: /health/ - port: 8000 - initialDelaySeconds: 10 - periodSeconds: 30 - livenessProbe: - httpGet: - path: /health/ - port: 8000 - initialDelaySeconds: 10 - periodSeconds: 30 - resources: - requests: - cpu: 10m - memory: 100Mi - limits: - cpu: "2" - memory: 2Gi - volumeMounts: - - name: djangoblog - mountPath: /code/djangoblog/collectedstatic - - name: resource - mountPath: /resource - volumes: - - name: djangoblog - persistentVolumeClaim: - claimName: djangoblog-pvc - - name: resource - persistentVolumeClaim: - claimName: resource-pvc - ---- -apiVersion: apps/v1 -kind: Deployment -metadata: - name: redis - namespace: djangoblog - labels: - app: redis -spec: - replicas: 1 - selector: - matchLabels: - app: redis - template: - metadata: - labels: - app: redis - spec: - containers: - - name: redis - image: redis:latest - imagePullPolicy: IfNotPresent - ports: - - containerPort: 6379 - resources: - requests: - cpu: 10m - memory: 100Mi - limits: - cpu: 200m - memory: 2Gi - ---- -apiVersion: apps/v1 -kind: Deployment -metadata: - name: db - namespace: djangoblog - labels: - app: db -spec: - replicas: 1 - selector: - matchLabels: - app: db - template: - metadata: - labels: - app: db - spec: - containers: - - name: db - image: mysql:latest - imagePullPolicy: IfNotPresent - ports: - - containerPort: 3306 - envFrom: - - configMapRef: - name: djangoblog-env - readinessProbe: - exec: - command: - - mysqladmin - - ping - - "-h" - - "127.0.0.1" - - "-u" - - "root" - - "-p$MYSQL_ROOT_PASSWORD" - initialDelaySeconds: 10 - periodSeconds: 10 - livenessProbe: - exec: - command: - - mysqladmin - - ping - - "-h" - - "127.0.0.1" - - "-u" - - "root" - - "-p$MYSQL_ROOT_PASSWORD" - initialDelaySeconds: 10 - periodSeconds: 10 - resources: - requests: - cpu: 10m - memory: 100Mi - limits: - cpu: "2" - memory: 2Gi - volumeMounts: - - name: db-data - mountPath: /var/lib/mysql - volumes: - - name: db-data - persistentVolumeClaim: - claimName: db-pvc - ---- -apiVersion: apps/v1 -kind: Deployment -metadata: - name: nginx - namespace: djangoblog - labels: - app: nginx -spec: - replicas: 1 - selector: - matchLabels: - app: nginx - template: - metadata: - labels: - app: nginx - spec: - containers: - - name: nginx - image: nginx:latest - imagePullPolicy: IfNotPresent - ports: - - containerPort: 80 - resources: - requests: - cpu: 10m - memory: 100Mi - limits: - cpu: "2" - memory: 2Gi - volumeMounts: - - name: nginx-config - mountPath: /etc/nginx/nginx.conf - subPath: nginx.conf - - name: nginx-config - mountPath: /etc/nginx/conf.d/default.conf - subPath: djangoblog.conf - - name: nginx-config - mountPath: /etc/nginx/conf.d/resource.lylinux.net.conf - subPath: resource.lylinux.net.conf - - name: nginx-config - mountPath: /etc/nginx/lylinux/resource.conf - subPath: lylinux.resource.conf - - name: djangoblog-pvc - mountPath: /code/djangoblog/collectedstatic - - name: resource-pvc - mountPath: /resource - volumes: - - name: nginx-config - configMap: - name: web-nginx-config - - name: djangoblog-pvc - persistentVolumeClaim: - claimName: djangoblog-pvc - - name: resource-pvc - persistentVolumeClaim: - claimName: resource-pvc - ---- -apiVersion: apps/v1 -kind: Deployment -metadata: - name: elasticsearch - namespace: djangoblog - labels: - app: elasticsearch -spec: - replicas: 1 - selector: - matchLabels: - app: elasticsearch - template: - metadata: - labels: - app: elasticsearch - spec: - containers: - - name: elasticsearch - image: liangliangyy/elasticsearch-analysis-ik:8.6.1 - imagePullPolicy: IfNotPresent - env: - - name: discovery.type - value: single-node - - name: ES_JAVA_OPTS - value: "-Xms256m -Xmx256m" - - name: xpack.security.enabled - value: "false" - - name: xpack.monitoring.templates.enabled - value: "false" - ports: - - containerPort: 9200 - resources: - requests: - cpu: 10m - memory: 100Mi - limits: - cpu: "2" - memory: 2Gi - readinessProbe: - httpGet: - path: / - port: 9200 - initialDelaySeconds: 15 - periodSeconds: 30 - livenessProbe: - httpGet: - path: / - port: 9200 - initialDelaySeconds: 15 - periodSeconds: 30 - volumeMounts: - - name: elasticsearch-data - mountPath: /usr/share/elasticsearch/data/ - volumes: - - name: elasticsearch-data - persistentVolumeClaim: - claimName: elasticsearch-pvc diff --git a/src/DjangoBlog/deploy/k8s/gateway.yaml b/src/DjangoBlog/deploy/k8s/gateway.yaml deleted file mode 100644 index a8de073..0000000 --- a/src/DjangoBlog/deploy/k8s/gateway.yaml +++ /dev/null @@ -1,17 +0,0 @@ -apiVersion: networking.k8s.io/v1 -kind: Ingress -metadata: - name: nginx - namespace: djangoblog -spec: - ingressClassName: nginx - rules: - - http: - paths: - - path: / - pathType: Prefix - backend: - service: - name: nginx - port: - number: 80 \ No newline at end of file diff --git a/src/DjangoBlog/deploy/k8s/pv.yaml b/src/DjangoBlog/deploy/k8s/pv.yaml deleted file mode 100644 index 874b72f..0000000 --- a/src/DjangoBlog/deploy/k8s/pv.yaml +++ /dev/null @@ -1,94 +0,0 @@ -apiVersion: v1 -kind: PersistentVolume -metadata: - name: local-pv-db -spec: - capacity: - storage: 10Gi - volumeMode: Filesystem - accessModes: - - ReadWriteOnce - persistentVolumeReclaimPolicy: Retain - storageClassName: local-storage - local: - path: /mnt/local-storage-db - nodeAffinity: - required: - nodeSelectorTerms: - - matchExpressions: - - key: kubernetes.io/hostname - operator: In - values: - - master ---- -apiVersion: v1 -kind: PersistentVolume -metadata: - name: local-pv-djangoblog -spec: - capacity: - storage: 5Gi - volumeMode: Filesystem - accessModes: - - ReadWriteOnce - persistentVolumeReclaimPolicy: Retain - storageClassName: local-storage - local: - path: /mnt/local-storage-djangoblog - nodeAffinity: - required: - nodeSelectorTerms: - - matchExpressions: - - key: kubernetes.io/hostname - operator: In - values: - - master - - ---- -apiVersion: v1 -kind: PersistentVolume -metadata: - name: local-pv-resource -spec: - capacity: - storage: 5Gi - volumeMode: Filesystem - accessModes: - - ReadWriteOnce - persistentVolumeReclaimPolicy: Retain - storageClassName: local-storage - local: - path: /mnt/resource/ - nodeAffinity: - required: - nodeSelectorTerms: - - matchExpressions: - - key: kubernetes.io/hostname - operator: In - values: - - master - ---- -apiVersion: v1 -kind: PersistentVolume -metadata: - name: local-pv-elasticsearch -spec: - capacity: - storage: 5Gi - volumeMode: Filesystem - accessModes: - - ReadWriteOnce - persistentVolumeReclaimPolicy: Retain - storageClassName: local-storage - local: - path: /mnt/local-storage-elasticsearch - nodeAffinity: - required: - nodeSelectorTerms: - - matchExpressions: - - key: kubernetes.io/hostname - operator: In - values: - - master \ No newline at end of file diff --git a/src/DjangoBlog/deploy/k8s/pvc.yaml b/src/DjangoBlog/deploy/k8s/pvc.yaml deleted file mode 100644 index ef238c5..0000000 --- a/src/DjangoBlog/deploy/k8s/pvc.yaml +++ /dev/null @@ -1,60 +0,0 @@ -apiVersion: v1 -kind: PersistentVolumeClaim -metadata: - name: db-pvc - namespace: djangoblog -spec: - storageClassName: local-storage - volumeName: local-pv-db - accessModes: - - ReadWriteOnce - resources: - requests: - storage: 10Gi - - ---- -apiVersion: v1 -kind: PersistentVolumeClaim -metadata: - name: djangoblog-pvc - namespace: djangoblog -spec: - volumeName: local-pv-djangoblog - storageClassName: local-storage - accessModes: - - ReadWriteOnce - resources: - requests: - storage: 5Gi - ---- -apiVersion: v1 -kind: PersistentVolumeClaim -metadata: - name: resource-pvc - namespace: djangoblog -spec: - volumeName: local-pv-resource - storageClassName: local-storage - accessModes: - - ReadWriteOnce - resources: - requests: - storage: 5Gi - ---- -apiVersion: v1 -kind: PersistentVolumeClaim -metadata: - name: elasticsearch-pvc - namespace: djangoblog -spec: - volumeName: local-pv-elasticsearch - storageClassName: local-storage - accessModes: - - ReadWriteOnce - resources: - requests: - storage: 5Gi - \ No newline at end of file diff --git a/src/DjangoBlog/deploy/k8s/service.yaml b/src/DjangoBlog/deploy/k8s/service.yaml deleted file mode 100644 index 4ef2931..0000000 --- a/src/DjangoBlog/deploy/k8s/service.yaml +++ /dev/null @@ -1,80 +0,0 @@ -apiVersion: v1 -kind: Service -metadata: - name: djangoblog - namespace: djangoblog - labels: - app: djangoblog -spec: - selector: - app: djangoblog - ports: - - protocol: TCP - port: 8000 - targetPort: 8000 - type: ClusterIP ---- -apiVersion: v1 -kind: Service -metadata: - name: nginx - namespace: djangoblog - labels: - app: nginx -spec: - selector: - app: nginx - ports: - - protocol: TCP - port: 80 - targetPort: 80 - type: ClusterIP ---- -apiVersion: v1 -kind: Service -metadata: - name: redis - namespace: djangoblog - labels: - app: redis -spec: - selector: - app: redis - ports: - - protocol: TCP - port: 6379 - targetPort: 6379 - type: ClusterIP ---- -apiVersion: v1 -kind: Service -metadata: - name: db - namespace: djangoblog - labels: - app: db -spec: - selector: - app: db - ports: - - protocol: TCP - port: 3306 - targetPort: 3306 - type: ClusterIP ---- -apiVersion: v1 -kind: Service -metadata: - name: elasticsearch - namespace: djangoblog - labels: - app: elasticsearch -spec: - selector: - app: elasticsearch - ports: - - protocol: TCP - port: 9200 - targetPort: 9200 - type: ClusterIP - diff --git a/src/DjangoBlog/deploy/k8s/storageclass.yaml b/src/DjangoBlog/deploy/k8s/storageclass.yaml deleted file mode 100644 index 5d5a14c..0000000 --- a/src/DjangoBlog/deploy/k8s/storageclass.yaml +++ /dev/null @@ -1,10 +0,0 @@ -apiVersion: storage.k8s.io/v1 -kind: StorageClass -metadata: - name: local-storage - annotations: - storageclass.kubernetes.io/is-default-class: "true" -provisioner: kubernetes.io/no-provisioner -volumeBindingMode: Immediate - - diff --git a/src/DjangoBlog/deploy/nginx.conf b/src/DjangoBlog/deploy/nginx.conf deleted file mode 100644 index 32161d8..0000000 --- a/src/DjangoBlog/deploy/nginx.conf +++ /dev/null @@ -1,50 +0,0 @@ -user nginx; -worker_processes auto; - -error_log /var/log/nginx/error.log notice; -pid /var/run/nginx.pid; - - -events { - worker_connections 1024; -} - - -http { - include /etc/nginx/mime.types; - default_type application/octet-stream; - - log_format main '$remote_addr - $remote_user [$time_local] "$request" ' - '$status $body_bytes_sent "$http_referer" ' - '"$http_user_agent" "$http_x_forwarded_for"'; - - access_log /var/log/nginx/access.log main; - - sendfile on; - #tcp_nopush on; - - keepalive_timeout 65; - - #gzip on; - - server { - root /code/djangoblog/collectedstatic/; - listen 80; - keepalive_timeout 70; - location /static/ { - expires max; - alias /code/djangoblog/collectedstatic/; - } - location / { - proxy_set_header X-Real-IP $remote_addr; - proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; - proxy_set_header Host $http_host; - proxy_set_header X-NginX-Proxy true; - proxy_redirect off; - if (!-f $request_filename) { - proxy_pass http://djangoblog:8000; - break; - } - } - } -} diff --git a/src/DjangoBlog/djangoblog/__init__.py b/src/DjangoBlog/djangoblog/__init__.py deleted file mode 100644 index 1e205f4..0000000 --- a/src/DjangoBlog/djangoblog/__init__.py +++ /dev/null @@ -1 +0,0 @@ -default_app_config = 'djangoblog.apps.DjangoblogAppConfig' diff --git a/src/DjangoBlog/djangoblog/admin_site.py b/src/DjangoBlog/djangoblog/admin_site.py deleted file mode 100644 index f120405..0000000 --- a/src/DjangoBlog/djangoblog/admin_site.py +++ /dev/null @@ -1,64 +0,0 @@ -from django.contrib.admin import AdminSite -from django.contrib.admin.models import LogEntry -from django.contrib.sites.admin import SiteAdmin -from django.contrib.sites.models import Site - -from accounts.admin import * -from blog.admin import * -from blog.models import * -from comments.admin import * -from comments.models import * -from djangoblog.logentryadmin import LogEntryAdmin -from oauth.admin import * -from oauth.models import * -from owntracks.admin import * -from owntracks.models import * -from servermanager.admin import * -from servermanager.models import * - - -class DjangoBlogAdminSite(AdminSite): - site_header = 'djangoblog administration' - site_title = 'djangoblog site admin' - - def __init__(self, name='admin'): - super().__init__(name) - - def has_permission(self, request): - return request.user.is_superuser - - # def get_urls(self): - # urls = super().get_urls() - # from django.urls import path - # from blog.views import refresh_memcache - # - # my_urls = [ - # path('refresh/', self.admin_view(refresh_memcache), name="refresh"), - # ] - # return urls + my_urls - - -admin_site = DjangoBlogAdminSite(name='admin') - -admin_site.register(Article, ArticlelAdmin) -admin_site.register(Category, CategoryAdmin) -admin_site.register(Tag, TagAdmin) -admin_site.register(Links, LinksAdmin) -admin_site.register(SideBar, SideBarAdmin) -admin_site.register(BlogSettings, BlogSettingsAdmin) - -admin_site.register(commands, CommandsAdmin) -admin_site.register(EmailSendLog, EmailSendLogAdmin) - -admin_site.register(BlogUser, BlogUserAdmin) - -admin_site.register(Comment, CommentAdmin) - -admin_site.register(OAuthUser, OAuthUserAdmin) -admin_site.register(OAuthConfig, OAuthConfigAdmin) - -admin_site.register(OwnTrackLog, OwnTrackLogsAdmin) - -admin_site.register(Site, SiteAdmin) - -admin_site.register(LogEntry, LogEntryAdmin) diff --git a/src/DjangoBlog/djangoblog/apps.py b/src/DjangoBlog/djangoblog/apps.py deleted file mode 100644 index d29e318..0000000 --- a/src/DjangoBlog/djangoblog/apps.py +++ /dev/null @@ -1,11 +0,0 @@ -from django.apps import AppConfig - -class DjangoblogAppConfig(AppConfig): - default_auto_field = 'django.db.models.BigAutoField' - name = 'djangoblog' - - def ready(self): - super().ready() - # Import and load plugins here - from .plugin_manage.loader import load_plugins - load_plugins() \ No newline at end of file diff --git a/src/DjangoBlog/djangoblog/blog_signals.py b/src/DjangoBlog/djangoblog/blog_signals.py deleted file mode 100644 index 393f441..0000000 --- a/src/DjangoBlog/djangoblog/blog_signals.py +++ /dev/null @@ -1,122 +0,0 @@ -import _thread -import logging - -import django.dispatch -from django.conf import settings -from django.contrib.admin.models import LogEntry -from django.contrib.auth.signals import user_logged_in, user_logged_out -from django.core.mail import EmailMultiAlternatives -from django.db.models.signals import post_save -from django.dispatch import receiver - -from comments.models import Comment -from comments.utils import send_comment_email -from djangoblog.spider_notify import SpiderNotify -from djangoblog.utils import cache, expire_view_cache, delete_sidebar_cache, delete_view_cache -from djangoblog.utils import get_current_site -from oauth.models import OAuthUser - -logger = logging.getLogger(__name__) - -oauth_user_login_signal = django.dispatch.Signal(['id']) -send_email_signal = django.dispatch.Signal( - ['emailto', 'title', 'content']) - - -@receiver(send_email_signal) -def send_email_signal_handler(sender, **kwargs): - emailto = kwargs['emailto'] - title = kwargs['title'] - content = kwargs['content'] - - msg = EmailMultiAlternatives( - title, - content, - from_email=settings.DEFAULT_FROM_EMAIL, - to=emailto) - msg.content_subtype = "html" - - from servermanager.models import EmailSendLog - log = EmailSendLog() - log.title = title - log.content = content - log.emailto = ','.join(emailto) - - try: - result = msg.send() - log.send_result = result > 0 - except Exception as e: - logger.error(f"失败邮箱号: {emailto}, {e}") - log.send_result = False - log.save() - - -@receiver(oauth_user_login_signal) -def oauth_user_login_signal_handler(sender, **kwargs): - id = kwargs['id'] - oauthuser = OAuthUser.objects.get(id=id) - site = get_current_site().domain - if oauthuser.picture and not oauthuser.picture.find(site) >= 0: - from djangoblog.utils import save_user_avatar - oauthuser.picture = save_user_avatar(oauthuser.picture) - oauthuser.save() - - delete_sidebar_cache() - - -@receiver(post_save) -def model_post_save_callback( - sender, - instance, - created, - raw, - using, - update_fields, - **kwargs): - clearcache = False - if isinstance(instance, LogEntry): - return - if 'get_full_url' in dir(instance): - is_update_views = update_fields == {'views'} - if not settings.TESTING and not is_update_views: - try: - notify_url = instance.get_full_url() - SpiderNotify.baidu_notify([notify_url]) - except Exception as ex: - logger.error("notify sipder", ex) - if not is_update_views: - clearcache = True - - if isinstance(instance, Comment): - if instance.is_enable: - path = instance.article.get_absolute_url() - site = get_current_site().domain - if site.find(':') > 0: - site = site[0:site.find(':')] - - expire_view_cache( - path, - servername=site, - serverport=80, - key_prefix='blogdetail') - if cache.get('seo_processor'): - cache.delete('seo_processor') - comment_cache_key = 'article_comments_{id}'.format( - id=instance.article.id) - cache.delete(comment_cache_key) - delete_sidebar_cache() - delete_view_cache('article_comments', [str(instance.article.pk)]) - - _thread.start_new_thread(send_comment_email, (instance,)) - - if clearcache: - cache.clear() - - -@receiver(user_logged_in) -@receiver(user_logged_out) -def user_auth_callback(sender, request, user, **kwargs): - if user and user.username: - logger.info(user) - delete_sidebar_cache() - # cache.clear() diff --git a/src/DjangoBlog/djangoblog/elasticsearch_backend.py b/src/DjangoBlog/djangoblog/elasticsearch_backend.py deleted file mode 100644 index 4afe498..0000000 --- a/src/DjangoBlog/djangoblog/elasticsearch_backend.py +++ /dev/null @@ -1,183 +0,0 @@ -from django.utils.encoding import force_str -from elasticsearch_dsl import Q -from haystack.backends import BaseEngine, BaseSearchBackend, BaseSearchQuery, log_query -from haystack.forms import ModelSearchForm -from haystack.models import SearchResult -from haystack.utils import log as logging - -from blog.documents import ArticleDocument, ArticleDocumentManager -from blog.models import Article - -logger = logging.getLogger(__name__) - - -class ElasticSearchBackend(BaseSearchBackend): - def __init__(self, connection_alias, **connection_options): - super( - ElasticSearchBackend, - self).__init__( - connection_alias, - **connection_options) - self.manager = ArticleDocumentManager() - self.include_spelling = True - - def _get_models(self, iterable): - models = iterable if iterable and iterable[0] else Article.objects.all() - docs = self.manager.convert_to_doc(models) - return docs - - def _create(self, models): - self.manager.create_index() - docs = self._get_models(models) - self.manager.rebuild(docs) - - def _delete(self, models): - for m in models: - m.delete() - return True - - def _rebuild(self, models): - models = models if models else Article.objects.all() - docs = self.manager.convert_to_doc(models) - self.manager.update_docs(docs) - - def update(self, index, iterable, commit=True): - - models = self._get_models(iterable) - self.manager.update_docs(models) - - def remove(self, obj_or_string): - models = self._get_models([obj_or_string]) - self._delete(models) - - def clear(self, models=None, commit=True): - self.remove(None) - - @staticmethod - def get_suggestion(query: str) -> str: - """获取推荐词, 如果没有找到添加原搜索词""" - - search = ArticleDocument.search() \ - .query("match", body=query) \ - .suggest('suggest_search', query, term={'field': 'body'}) \ - .execute() - - keywords = [] - for suggest in search.suggest.suggest_search: - if suggest["options"]: - keywords.append(suggest["options"][0]["text"]) - else: - keywords.append(suggest["text"]) - - return ' '.join(keywords) - - @log_query - def search(self, query_string, **kwargs): - logger.info('search query_string:' + query_string) - - start_offset = kwargs.get('start_offset') - end_offset = kwargs.get('end_offset') - - # 推荐词搜索 - if getattr(self, "is_suggest", None): - suggestion = self.get_suggestion(query_string) - else: - suggestion = query_string - - q = Q('bool', - should=[Q('match', body=suggestion), Q('match', title=suggestion)], - minimum_should_match="70%") - - search = ArticleDocument.search() \ - .query('bool', filter=[q]) \ - .filter('term', status='p') \ - .filter('term', type='a') \ - .source(False)[start_offset: end_offset] - - results = search.execute() - hits = results['hits'].total - raw_results = [] - for raw_result in results['hits']['hits']: - app_label = 'blog' - model_name = 'Article' - additional_fields = {} - - result_class = SearchResult - - result = result_class( - app_label, - model_name, - raw_result['_id'], - raw_result['_score'], - **additional_fields) - raw_results.append(result) - facets = {} - spelling_suggestion = None if query_string == suggestion else suggestion - - return { - 'results': raw_results, - 'hits': hits, - 'facets': facets, - 'spelling_suggestion': spelling_suggestion, - } - - -class ElasticSearchQuery(BaseSearchQuery): - def _convert_datetime(self, date): - if hasattr(date, 'hour'): - return force_str(date.strftime('%Y%m%d%H%M%S')) - else: - return force_str(date.strftime('%Y%m%d000000')) - - def clean(self, query_fragment): - """ - Provides a mechanism for sanitizing user input before presenting the - value to the backend. - - Whoosh 1.X differs here in that you can no longer use a backslash - to escape reserved characters. Instead, the whole word should be - quoted. - """ - words = query_fragment.split() - cleaned_words = [] - - for word in words: - if word in self.backend.RESERVED_WORDS: - word = word.replace(word, word.lower()) - - for char in self.backend.RESERVED_CHARACTERS: - if char in word: - word = "'%s'" % word - break - - cleaned_words.append(word) - - return ' '.join(cleaned_words) - - def build_query_fragment(self, field, filter_type, value): - return value.query_string - - def get_count(self): - results = self.get_results() - return len(results) if results else 0 - - def get_spelling_suggestion(self, preferred_query=None): - return self._spelling_suggestion - - def build_params(self, spelling_query=None): - kwargs = super(ElasticSearchQuery, self).build_params(spelling_query=spelling_query) - return kwargs - - -class ElasticSearchModelSearchForm(ModelSearchForm): - - def search(self): - # 是否建议搜索 - self.searchqueryset.query.backend.is_suggest = self.data.get("is_suggest") != "no" - sqs = super().search() - return sqs - - -class ElasticSearchEngine(BaseEngine): - backend = ElasticSearchBackend - query = ElasticSearchQuery diff --git a/src/DjangoBlog/djangoblog/feeds.py b/src/DjangoBlog/djangoblog/feeds.py deleted file mode 100644 index 8c4e851..0000000 --- a/src/DjangoBlog/djangoblog/feeds.py +++ /dev/null @@ -1,40 +0,0 @@ -from django.contrib.auth import get_user_model -from django.contrib.syndication.views import Feed -from django.utils import timezone -from django.utils.feedgenerator import Rss201rev2Feed - -from blog.models import Article -from djangoblog.utils import CommonMarkdown - - -class DjangoBlogFeed(Feed): - feed_type = Rss201rev2Feed - - description = '大巧无工,重剑无锋.' - title = "且听风吟 大巧无工,重剑无锋. " - link = "/feed/" - - def author_name(self): - return get_user_model().objects.first().nickname - - def author_link(self): - return get_user_model().objects.first().get_absolute_url() - - def items(self): - return Article.objects.filter(type='a', status='p').order_by('-pub_time')[:5] - - def item_title(self, item): - return item.title - - def item_description(self, item): - return CommonMarkdown.get_markdown(item.body) - - def feed_copyright(self): - now = timezone.now() - return "Copyright© {year} 且听风吟".format(year=now.year) - - def item_link(self, item): - return item.get_absolute_url() - - def item_guid(self, item): - return diff --git a/src/DjangoBlog/djangoblog/logentryadmin.py b/src/DjangoBlog/djangoblog/logentryadmin.py deleted file mode 100644 index 2f6a535..0000000 --- a/src/DjangoBlog/djangoblog/logentryadmin.py +++ /dev/null @@ -1,91 +0,0 @@ -from django.contrib import admin -from django.contrib.admin.models import DELETION -from django.contrib.contenttypes.models import ContentType -from django.urls import reverse, NoReverseMatch -from django.utils.encoding import force_str -from django.utils.html import escape -from django.utils.safestring import mark_safe -from django.utils.translation import gettext_lazy as _ - - -class LogEntryAdmin(admin.ModelAdmin): - list_filter = [ - 'content_type' - ] - - search_fields = [ - 'object_repr', - 'change_message' - ] - - list_display_links = [ - 'action_time', - 'get_change_message', - ] - list_display = [ - 'action_time', - 'user_link', - 'content_type', - 'object_link', - 'get_change_message', - ] - - def has_add_permission(self, request): - return False - - def has_change_permission(self, request, obj=None): - return ( - request.user.is_superuser or - request.user.has_perm('admin.change_logentry') - ) and request.method != 'POST' - - def has_delete_permission(self, request, obj=None): - return False - - def object_link(self, obj): - object_link = escape(obj.object_repr) - content_type = obj.content_type - - if obj.action_flag != DELETION and content_type is not None: - # try returning an actual link instead of object repr string - try: - url = reverse( - 'admin:{}_{}_change'.format(content_type.app_label, - content_type.model), - args=[obj.object_id] - ) - object_link = '{}'.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/base_plugin.py b/src/DjangoBlog/djangoblog/plugin_manage/base_plugin.py deleted file mode 100644 index df1ce0b..0000000 --- a/src/DjangoBlog/djangoblog/plugin_manage/base_plugin.py +++ /dev/null @@ -1,194 +0,0 @@ -import logging -from pathlib import Path - -from django.template import TemplateDoesNotExist -from django.template.loader import render_to_string - -logger = logging.getLogger(__name__) - - -class BasePlugin: - # 插件元数据 - PLUGIN_NAME = None - PLUGIN_DESCRIPTION = None - PLUGIN_VERSION = None - PLUGIN_AUTHOR = None - - # 插件配置 - SUPPORTED_POSITIONS = [] # 支持的显示位置 - DEFAULT_PRIORITY = 100 # 默认优先级(数字越小优先级越高) - POSITION_PRIORITIES = {} # 各位置的优先级 {'sidebar': 50, 'article_bottom': 80} - - def __init__(self): - if not all([self.PLUGIN_NAME, self.PLUGIN_DESCRIPTION, self.PLUGIN_VERSION]): - raise ValueError("Plugin metadata (PLUGIN_NAME, PLUGIN_DESCRIPTION, PLUGIN_VERSION) must be defined.") - - # 设置插件路径 - self.plugin_dir = self._get_plugin_directory() - self.plugin_slug = self._get_plugin_slug() - - self.init_plugin() - self.register_hooks() - - def _get_plugin_directory(self): - """获取插件目录路径""" - import inspect - plugin_file = inspect.getfile(self.__class__) - return Path(plugin_file).parent - - def _get_plugin_slug(self): - """获取插件标识符(目录名)""" - return self.plugin_dir.name - - def init_plugin(self): - """ - 插件初始化逻辑 - 子类可以重写此方法来实现特定的初始化操作 - """ - logger.info(f'{self.PLUGIN_NAME} initialized.') - - def register_hooks(self): - """ - 注册插件钩子 - 子类可以重写此方法来注册特定的钩子 - """ - pass - - # === 位置渲染系统 === - def render_position_widget(self, position, context, **kwargs): - """ - 根据位置渲染插件组件 - - Args: - position: 位置标识 - context: 模板上下文 - **kwargs: 额外参数 - - Returns: - dict: {'html': 'HTML内容', 'priority': 优先级} 或 None - """ - if position not in self.SUPPORTED_POSITIONS: - return None - - # 检查条件显示 - if not self.should_display(position, context, **kwargs): - return None - - # 调用具体的位置渲染方法 - method_name = f'render_{position}_widget' - if hasattr(self, method_name): - html = getattr(self, method_name)(context, **kwargs) - if html: - priority = self.POSITION_PRIORITIES.get(position, self.DEFAULT_PRIORITY) - return { - 'html': html, - 'priority': priority, - 'plugin_name': self.PLUGIN_NAME - } - - return None - - def should_display(self, position, context, **kwargs): - """ - 判断插件是否应该在指定位置显示 - 子类可重写此方法实现条件显示逻辑 - - Args: - position: 位置标识 - context: 模板上下文 - **kwargs: 额外参数 - - Returns: - bool: 是否显示 - """ - return True - - # === 各位置渲染方法 - 子类重写 === - def render_sidebar_widget(self, context, **kwargs): - """渲染侧边栏组件""" - return None - - def render_article_bottom_widget(self, context, **kwargs): - """渲染文章底部组件""" - return None - - def render_article_top_widget(self, context, **kwargs): - """渲染文章顶部组件""" - return None - - def render_header_widget(self, context, **kwargs): - """渲染页头组件""" - return None - - def render_footer_widget(self, context, **kwargs): - """渲染页脚组件""" - return None - - def render_comment_before_widget(self, context, **kwargs): - """渲染评论前组件""" - return None - - def render_comment_after_widget(self, context, **kwargs): - """渲染评论后组件""" - return None - - # === 模板系统 === - def render_template(self, template_name, context=None): - """ - 渲染插件模板 - - Args: - template_name: 模板文件名 - context: 模板上下文 - - Returns: - HTML字符串 - """ - if context is None: - context = {} - - template_path = f"plugins/{self.plugin_slug}/{template_name}" - - try: - return render_to_string(template_path, context) - except TemplateDoesNotExist: - logger.warning(f"Plugin template not found: {template_path}") - return "" - - # === 静态资源系统 === - def get_static_url(self, static_file): - """获取插件静态文件URL""" - from django.templatetags.static import static - return static(f"{self.plugin_slug}/static/{self.plugin_slug}/{static_file}") - - def get_css_files(self): - """获取插件CSS文件列表""" - return [] - - def get_js_files(self): - """获取插件JavaScript文件列表""" - return [] - - def get_head_html(self, context=None): - """获取需要插入到中的HTML内容""" - return "" - - def get_body_html(self, context=None): - """获取需要插入到底部的HTML内容""" - return "" - - def get_plugin_info(self): - """ - 获取插件信息 - :return: 包含插件元数据的字典 - """ - return { - 'name': self.PLUGIN_NAME, - 'description': self.PLUGIN_DESCRIPTION, - 'version': self.PLUGIN_VERSION, - 'author': self.PLUGIN_AUTHOR, - 'slug': self.plugin_slug, - 'directory': str(self.plugin_dir), - 'supported_positions': self.SUPPORTED_POSITIONS, - 'priorities': self.POSITION_PRIORITIES - } diff --git a/src/DjangoBlog/djangoblog/plugin_manage/hook_constants.py b/src/DjangoBlog/djangoblog/plugin_manage/hook_constants.py deleted file mode 100644 index 8ed4e89..0000000 --- a/src/DjangoBlog/djangoblog/plugin_manage/hook_constants.py +++ /dev/null @@ -1,22 +0,0 @@ -ARTICLE_DETAIL_LOAD = 'article_detail_load' -ARTICLE_CREATE = 'article_create' -ARTICLE_UPDATE = 'article_update' -ARTICLE_DELETE = 'article_delete' - -ARTICLE_CONTENT_HOOK_NAME = "the_content" - -# 位置钩子常量 -POSITION_HOOKS = { - 'article_top': 'article_top_widgets', - 'article_bottom': 'article_bottom_widgets', - 'sidebar': 'sidebar_widgets', - 'header': 'header_widgets', - 'footer': 'footer_widgets', - 'comment_before': 'comment_before_widgets', - 'comment_after': 'comment_after_widgets', -} - -# 资源注入钩子 -HEAD_RESOURCES_HOOK = 'head_resources' -BODY_RESOURCES_HOOK = 'body_resources' - diff --git a/src/DjangoBlog/djangoblog/plugin_manage/hooks.py b/src/DjangoBlog/djangoblog/plugin_manage/hooks.py deleted file mode 100644 index d712540..0000000 --- a/src/DjangoBlog/djangoblog/plugin_manage/hooks.py +++ /dev/null @@ -1,44 +0,0 @@ -import logging - -logger = logging.getLogger(__name__) - -_hooks = {} - - -def register(hook_name: str, callback: callable): - """ - 注册一个钩子回调。 - """ - if hook_name not in _hooks: - _hooks[hook_name] = [] - _hooks[hook_name].append(callback) - logger.debug(f"Registered hook '{hook_name}' with callback '{callback.__name__}'") - - -def run_action(hook_name: str, *args, **kwargs): - """ - 执行一个 Action Hook。 - 它会按顺序执行所有注册到该钩子上的回调函数。 - """ - if hook_name in _hooks: - logger.debug(f"Running action hook '{hook_name}'") - for callback in _hooks[hook_name]: - try: - callback(*args, **kwargs) - except Exception as e: - logger.error(f"Error running action hook '{hook_name}' callback '{callback.__name__}': {e}", exc_info=True) - - -def apply_filters(hook_name: str, value, *args, **kwargs): - """ - 执行一个 Filter Hook。 - 它会把 value 依次传递给所有注册的回调函数进行处理。 - """ - if hook_name in _hooks: - logger.debug(f"Applying filter hook '{hook_name}'") - for callback in _hooks[hook_name]: - try: - value = callback(value, *args, **kwargs) - except Exception as e: - logger.error(f"Error applying filter hook '{hook_name}' callback '{callback.__name__}': {e}", exc_info=True) - return value diff --git a/src/DjangoBlog/djangoblog/plugin_manage/loader.py b/src/DjangoBlog/djangoblog/plugin_manage/loader.py deleted file mode 100644 index ee750d0..0000000 --- a/src/DjangoBlog/djangoblog/plugin_manage/loader.py +++ /dev/null @@ -1,64 +0,0 @@ -import os -import logging -from django.conf import settings - -logger = logging.getLogger(__name__) - -# 全局插件注册表 -_loaded_plugins = [] - -def load_plugins(): - """ - Dynamically loads and initializes plugins from the 'plugins' directory. - This function is intended to be called when the Django app registry is ready. - """ - global _loaded_plugins - _loaded_plugins = [] - - for plugin_name in settings.ACTIVE_PLUGINS: - plugin_path = os.path.join(settings.PLUGINS_DIR, plugin_name) - if os.path.isdir(plugin_path) and os.path.exists(os.path.join(plugin_path, 'plugin.py')): - try: - # 导入插件模块 - plugin_module = __import__(f'plugins.{plugin_name}.plugin', fromlist=['plugin']) - - # 获取插件实例 - if hasattr(plugin_module, 'plugin'): - plugin_instance = plugin_module.plugin - _loaded_plugins.append(plugin_instance) - logger.info(f"Successfully loaded plugin: {plugin_name} - {plugin_instance.PLUGIN_NAME}") - else: - logger.warning(f"Plugin {plugin_name} does not have 'plugin' instance") - - except ImportError as e: - logger.error(f"Failed to import plugin: {plugin_name}", exc_info=e) - except AttributeError as e: - logger.error(f"Failed to get plugin instance: {plugin_name}", exc_info=e) - except Exception as e: - logger.error(f"Unexpected error loading plugin: {plugin_name}", exc_info=e) - -def get_loaded_plugins(): - """获取所有已加载的插件""" - return _loaded_plugins - -def get_plugin_by_name(plugin_name): - """根据名称获取插件""" - for plugin in _loaded_plugins: - if plugin.plugin_slug == plugin_name: - return plugin - return None - -def get_plugin_by_slug(plugin_slug): - """根据slug获取插件""" - for plugin in _loaded_plugins: - if plugin.plugin_slug == plugin_slug: - return plugin - return None - -def get_plugins_info(): - """获取所有插件的信息""" - return [plugin.get_plugin_info() for plugin in _loaded_plugins] - -def get_plugins_by_position(position): - """获取支持指定位置的插件""" - return [plugin for plugin in _loaded_plugins if position in plugin.SUPPORTED_POSITIONS] \ No newline at end of file diff --git a/src/DjangoBlog/djangoblog/settings.py b/src/DjangoBlog/djangoblog/settings.py deleted file mode 100644 index 0bed20d..0000000 --- a/src/DjangoBlog/djangoblog/settings.py +++ /dev/null @@ -1,404 +0,0 @@ -""" -Django settings for djangoblog project. - -Generated by 'django-admin startproject' using Django 1.10.2. - -For more information on this file, see -https://docs.djangoproject.com/en/1.10/topics/settings/ - -For the full list of settings and their values, see -https://docs.djangoproject.com/en/1.10/ref/settings/ -""" -import 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' - - -# Build paths inside the project like this: BASE_DIR / 'subdir'. -BASE_DIR = Path(__file__).resolve().parent.parent - -# Quick-start development settings - unsuitable for production -# See https://docs.djangoproject.com/en/1.10/howto/deployment/checklist/ - -# SECURITY WARNING: keep the secret key used in production secret! -SECRET_KEY = os.environ.get( - 'DJANGO_SECRET_KEY') or 'n9ceqv38)#&mwuat@(mjb_p%em$e8$qyr#fw9ot!=ba6lijx-6' -# SECURITY WARNING: don't run with debug turned on in production! -DEBUG = env_to_bool('DJANGO_DEBUG', True) -# DEBUG = False -TESTING = len(sys.argv) > 1 and sys.argv[1] == 'test' - -# ALLOWED_HOSTS = [] -ALLOWED_HOSTS = ['*', '127.0.0.1', 'example.com'] -# django 4.0新增配置 -CSRF_TRUSTED_ORIGINS = ['http://example.com'] -# Application definition - - -INSTALLED_APPS = [ - # 'django.contrib.admin', - 'django.contrib.admin.apps.SimpleAdminConfig', - 'django.contrib.auth', - 'django.contrib.contenttypes', - 'django.contrib.sessions', - 'django.contrib.messages', - 'django.contrib.staticfiles', - 'django.contrib.sites', - 'django.contrib.sitemaps', - 'mdeditor', - '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', - # 'django.middleware.cache.UpdateCacheMiddleware', - 'django.middleware.common.CommonMiddleware', - # 'django.middleware.cache.FetchFromCacheMiddleware', - 'django.middleware.csrf.CsrfViewMiddleware', - 'django.contrib.auth.middleware.AuthenticationMiddleware', - 'django.contrib.messages.middleware.MessageMiddleware', - 'django.middleware.clickjacking.XFrameOptionsMiddleware', - 'django.middleware.http.ConditionalGetMiddleware', - 'blog.middleware.OnlineMiddleware' -] - -ROOT_URLCONF = 'djangoblog.urls' - -TEMPLATES = [ - { - 'BACKEND': 'django.template.backends.django.DjangoTemplates', - 'DIRS': [os.path.join(BASE_DIR, 'templates')], - 'APP_DIRS': True, - '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' - ], - }, - }, -] - -WSGI_APPLICATION = 'djangoblog.wsgi.application' - -# Database -# https://docs.djangoproject.com/en/1.10/ref/settings/#databases - - -DATABASES = { - 'default': { - 'ENGINE': 'django.db.backends.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 'root', - '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'}, - }} - -# Password validation -# https://docs.djangoproject.com/en/1.10/ref/settings/#auth-password-validators - -AUTH_PASSWORD_VALIDATORS = [ - { - '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', - }, -] - -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 - -# Static files (CSS, JavaScript, Images) -# https://docs.djangoproject.com/en/1.10/howto/static-files/ - - -HAYSTACK_CONNECTIONS = { - 'default': { - 'ENGINE': 'djangoblog.whoosh_cn_backend.WhooshEngine', - 'PATH': os.path.join(os.path.dirname(__file__), 'whoosh_index'), - }, -} -# Automatically update searching index -HAYSTACK_SIGNAL_PROCESSOR = 'haystack.signals.RealtimeSignalProcessor' -# Allow user login with username and password -AUTHENTICATION_BACKENDS = [ - 'accounts.user_login_backend.EmailOrUsernameModelBackend'] - -STATIC_ROOT = os.path.join(BASE_DIR, 'collectedstatic') - -STATIC_URL = '/static/' -STATICFILES = os.path.join(BASE_DIR, 'static') - -# 添加插件静态文件目录 -STATICFILES_DIRS = [ - os.path.join(BASE_DIR, 'plugins'), # 让Django能找到插件的静态文件 -] - -AUTH_USER_MODEL = 'accounts.BlogUser' -LOGIN_URL = '/login/' - -TIME_FORMAT = '%Y-%m-%d %H:%M:%S' -DATE_TIME_FORMAT = '%Y-%m-%d' - -# bootstrap color styles -BOOTSTRAP_COLOR_TYPES = [ - 'default', 'primary', 'success', 'info', 'warning', 'danger' -] - -# paginate -PAGINATE_BY = 10 -# http cache timeout -CACHE_CONTROL_MAX_AGE = 2592000 -# cache setting -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")}', - } - } - -SITE_ID = 1 -BAIDU_NOTIFY_URL = os.environ.get('DJANGO_BAIDU_NOTIFY_URL') \ - or 'http://data.zz.baidu.com/urls?site=https://www.lylinux.net&token=1uAOGrMsUm5syDGn' - -# Email: -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 -# Setting debug=false did NOT handle except email notifications -ADMINS = [('admin', os.environ.get('DJANGO_ADMIN_EMAIL') or 'admin@admin.com')] -# WX ADMIN password(Two times md5) -WXADMIN = os.environ.get( - 'DJANGO_WXADMIN_PASSWORD') or '995F03AC401D6CABABAEF756FC4D43C7' - -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, - } - } -} - -STATICFILES_FINDERS = ( - 'django.contrib.staticfiles.finders.FileSystemFinder', - 'django.contrib.staticfiles.finders.AppDirectoriesFinder', - # other - 'compressor.finders.CompressorFinder', -) -COMPRESS_ENABLED = True -# 根据环境变量决定是否启用离线压缩 -COMPRESS_OFFLINE = os.environ.get('COMPRESS_OFFLINE', 'False').lower() == 'true' - -# 压缩输出目录 -COMPRESS_OUTPUT_DIR = 'compressed' - -# 压缩文件名模板 - 包含哈希值用于缓存破坏 -COMPRESS_CSS_HASHING_METHOD = 'mtime' -COMPRESS_JS_HASHING_METHOD = 'mtime' - -# 高级CSS压缩过滤器 -COMPRESS_CSS_FILTERS = [ - # 创建绝对URL - 'compressor.filters.css_default.CssAbsoluteFilter', - # CSS压缩器 - 高压缩等级 - 'compressor.filters.cssmin.CSSCompressorFilter', -] - -# 高级JS压缩过滤器 -COMPRESS_JS_FILTERS = [ - # JS压缩器 - 高压缩等级 - 'compressor.filters.jsmin.SlimItFilter', -] - -# 压缩缓存配置 -COMPRESS_CACHE_BACKEND = 'default' -COMPRESS_CACHE_KEY_FUNCTION = 'compressor.cache.simple_cachekey' - -# 预压缩配置 -COMPRESS_PRECOMPILERS = ( - # 支持SCSS/SASS - ('text/x-scss', 'django_libsass.SassCompiler'), - ('text/x-sass', 'django_libsass.SassCompiler'), -) - -# 压缩性能优化 -COMPRESS_MINT_DELAY = 30 # 压缩延迟(秒) -COMPRESS_MTIME_DELAY = 10 # 修改时间检查延迟 -COMPRESS_REBUILD_TIMEOUT = 2592000 # 重建超时(30天) - -# 压缩等级配置 -COMPRESS_CSS_COMPRESSOR = 'compressor.css.CssCompressor' -COMPRESS_JS_COMPRESSOR = 'compressor.js.JsCompressor' - -# 静态文件缓存配置 -STATICFILES_STORAGE = 'django.contrib.staticfiles.storage.ManifestStaticFilesStorage' - -# 浏览器缓存配置(通过中间件或服务器配置) -COMPRESS_URL = STATIC_URL -COMPRESS_ROOT = STATIC_ROOT - -MEDIA_ROOT = os.path.join(BASE_DIR, 'uploads') -MEDIA_URL = '/media/' -X_FRAME_OPTIONS = 'SAMEORIGIN' - -# 安全头部配置 - 防XSS和其他攻击 -SECURE_BROWSER_XSS_FILTER = True -SECURE_CONTENT_TYPE_NOSNIFF = True -SECURE_REFERRER_POLICY = 'strict-origin-when-cross-origin' - -# 内容安全策略 (CSP) - 防XSS攻击 -CSP_DEFAULT_SRC = ["'self'"] -CSP_SCRIPT_SRC = ["'self'", "'unsafe-inline'", "cdn.mathjax.org", "*.googleapis.com"] -CSP_STYLE_SRC = ["'self'", "'unsafe-inline'", "*.googleapis.com", "*.gstatic.com"] -CSP_IMG_SRC = ["'self'", "data:", "*.lylinux.net", "*.gravatar.com", "*.githubusercontent.com"] -CSP_FONT_SRC = ["'self'", "*.googleapis.com", "*.gstatic.com"] -CSP_CONNECT_SRC = ["'self'"] -CSP_FRAME_SRC = ["'none'"] -CSP_OBJECT_SRC = ["'none'"] - -DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField' - -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', - }, - } - -# Plugin System -PLUGINS_DIR = BASE_DIR / 'plugins' -ACTIVE_PLUGINS = [ - 'article_copyright', - 'reading_time', - 'external_links', - 'view_count', - 'seo_optimizer', - 'image_lazy_loading', - 'article_recommendation', -] - diff --git a/src/DjangoBlog/djangoblog/sitemap.py b/src/DjangoBlog/djangoblog/sitemap.py deleted file mode 100644 index 8b7d446..0000000 --- a/src/DjangoBlog/djangoblog/sitemap.py +++ /dev/null @@ -1,59 +0,0 @@ -from django.contrib.sitemaps import Sitemap -from django.urls import reverse - -from blog.models import Article, Category, Tag - - -class StaticViewSitemap(Sitemap): - priority = 0.5 - changefreq = 'daily' - - def items(self): - return ['blog:index', ] - - def location(self, item): - return reverse(item) - - -class ArticleSiteMap(Sitemap): - changefreq = "monthly" - priority = "0.6" - - def items(self): - return Article.objects.filter(status='p') - - def lastmod(self, obj): - return obj.last_modify_time - - -class CategorySiteMap(Sitemap): - changefreq = "Weekly" - priority = "0.6" - - def items(self): - return Category.objects.all() - - def lastmod(self, obj): - return obj.last_modify_time - - -class TagSiteMap(Sitemap): - changefreq = "Weekly" - priority = "0.3" - - def items(self): - return Tag.objects.all() - - def lastmod(self, obj): - return obj.last_modify_time - - -class UserSiteMap(Sitemap): - changefreq = "Weekly" - priority = "0.3" - - def items(self): - return list(set(map(lambda x: x.author, Article.objects.all()))) - - def lastmod(self, obj): - return obj.date_joined diff --git a/src/DjangoBlog/djangoblog/spider_notify.py b/src/DjangoBlog/djangoblog/spider_notify.py deleted file mode 100644 index 7b909e9..0000000 --- a/src/DjangoBlog/djangoblog/spider_notify.py +++ /dev/null @@ -1,21 +0,0 @@ -import logging - -import requests -from django.conf import settings - -logger = logging.getLogger(__name__) - - -class SpiderNotify(): - @staticmethod - def baidu_notify(urls): - try: - data = '\n'.join(urls) - result = requests.post(settings.BAIDU_NOTIFY_URL, data=data) - logger.info(result.text) - except Exception as e: - logger.error(e) - - @staticmethod - def notify(url): - SpiderNotify.baidu_notify(url) diff --git a/src/DjangoBlog/djangoblog/tests.py b/src/DjangoBlog/djangoblog/tests.py deleted file mode 100644 index 01237d9..0000000 --- a/src/DjangoBlog/djangoblog/tests.py +++ /dev/null @@ -1,32 +0,0 @@ -from django.test import TestCase - -from djangoblog.utils import * - - -class DjangoBlogTest(TestCase): - def setUp(self): - pass - - def test_utils(self): - md5 = get_sha256('test') - self.assertIsNotNone(md5) - c = CommonMarkdown.get_markdown(''' - # Title1 - - ```python - import os - ``` - - [url](https://www.lylinux.net/) - - [ddd](http://www.baidu.com) - - - ''') - self.assertIsNotNone(c) - d = { - 'd': 'key1', - 'd2': 'key2' - } - data = parse_dict_to_url(d) - self.assertIsNotNone(data) diff --git a/src/DjangoBlog/djangoblog/urls.py b/src/DjangoBlog/djangoblog/urls.py deleted file mode 100644 index 6a9e1de..0000000 --- a/src/DjangoBlog/djangoblog/urls.py +++ /dev/null @@ -1,78 +0,0 @@ -"""djangoblog URL Configuration - -The `urlpatterns` list routes URLs to views. For more information please see: - https://docs.djangoproject.com/en/1.10/topics/http/urls/ -Examples: -Function views - 1. Add an import: from my_app import views - 2. Add a URL to urlpatterns: url(r'^$', views.home, name='home') -Class-based views - 1. Add an import: from other_app.views import Home - 2. Add a URL to urlpatterns: url(r'^$', Home.as_view(), name='home') -Including another URLconf - 1. Import the include() function: from django.conf.urls import url, include - 2. Add a URL to urlpatterns: url(r'^blog/', include('blog.urls')) -""" -from django.conf import settings -from django.conf.urls.i18n import i18n_patterns -from django.conf.urls.static import static -from django.contrib.sitemaps.views import sitemap -from django.urls import path, include -from django.urls import re_path -from haystack.views import search_view_factory -from django.http import JsonResponse -import time - -from blog.views import EsSearchView -from djangoblog.admin_site import admin_site -from djangoblog.elasticsearch_backend import ElasticSearchModelSearchForm -from djangoblog.feeds import DjangoBlogFeed -from djangoblog.sitemap import ArticleSiteMap, CategorySiteMap, StaticViewSitemap, TagSiteMap, UserSiteMap - -sitemaps = { - - 'blog': ArticleSiteMap, - 'Category': CategorySiteMap, - 'Tag': TagSiteMap, - 'User': UserSiteMap, - 'static': StaticViewSitemap -} - -handler404 = 'blog.views.page_not_found_view' -handler500 = 'blog.views.server_error_view' -handle403 = 'blog.views.permission_denied_view' - - -def health_check(request): - """ - 健康检查接口 - 简单返回服务健康状态 - """ - return JsonResponse({ - 'status': 'healthy', - 'timestamp': time.time() - }) - -urlpatterns = [ - path('i18n/', include('django.conf.urls.i18n')), - path('health/', health_check, name='health_check'), -] -urlpatterns += i18n_patterns( - re_path(r'^admin/', admin_site.urls), - re_path(r'', include('blog.urls', namespace='blog')), - re_path(r'mdeditor/', include('mdeditor.urls')), - re_path(r'', include('comments.urls', namespace='comment')), - re_path(r'', include('accounts.urls', namespace='account')), - re_path(r'', include('oauth.urls', namespace='oauth')), - re_path(r'^sitemap\.xml$', sitemap, {'sitemaps': sitemaps}, - name='django.contrib.sitemaps.views.sitemap'), - re_path(r'^feed/$', DjangoBlogFeed()), - re_path(r'^rss/$', DjangoBlogFeed()), - re_path('^search', search_view_factory(view_class=EsSearchView, form_class=ElasticSearchModelSearchForm), - name='search'), - re_path(r'', include('servermanager.urls', namespace='servermanager')), - re_path(r'', include('owntracks.urls', namespace='owntracks')) - , prefix_default_language=False) + static(settings.STATIC_URL, document_root=settings.STATIC_ROOT) -if settings.DEBUG: - urlpatterns += static(settings.MEDIA_URL, - document_root=settings.MEDIA_ROOT) diff --git a/src/DjangoBlog/djangoblog/utils.py b/src/DjangoBlog/djangoblog/utils.py deleted file mode 100644 index 91d2b91..0000000 --- a/src/DjangoBlog/djangoblog/utils.py +++ /dev/null @@ -1,272 +0,0 @@ -#!/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', 'span', 'div'] - -# 安全的class值白名单 - 只允许代码高亮相关的class -ALLOWED_CLASSES = [ - 'codehilite', 'highlight', 'hll', 'c', 'err', 'k', 'l', 'n', 'o', 'p', 'cm', 'cp', 'c1', 'cs', - 'gd', 'ge', 'gr', 'gh', 'gi', 'go', 'gp', 'gs', 'gu', 'gt', 'kc', 'kd', 'kn', 'kp', 'kr', 'kt', - 'ld', 'm', 'mf', 'mh', 'mi', 'mo', 'na', 'nb', 'nc', 'no', 'nd', 'ni', 'ne', 'nf', 'nl', 'nn', - 'nt', 'nv', 'ow', 'w', 'mb', 'mh', 'mi', 'mo', 'sb', 'sc', 'sd', 'se', 'sh', 'si', 'sx', 's2', - 's1', 'ss', 'bp', 'vc', 'vg', 'vi', 'il' -] - -def class_filter(tag, name, value): - """自定义class属性过滤器""" - if name == 'class': - # 只允许预定义的安全class值 - allowed_classes = [cls for cls in value.split() if cls in ALLOWED_CLASSES] - return ' '.join(allowed_classes) if allowed_classes else False - return value - -# 安全的属性白名单 -ALLOWED_ATTRIBUTES = { - 'a': ['href', 'title'], - 'abbr': ['title'], - 'acronym': ['title'], - 'span': class_filter, - 'div': class_filter, - 'pre': class_filter, - 'code': class_filter -} - -# 安全的协议白名单 - 防止javascript:等危险协议 -ALLOWED_PROTOCOLS = ['http', 'https', 'mailto'] - -def sanitize_html(html): - """ - 安全的HTML清理函数 - 使用bleach库进行白名单过滤,防止XSS攻击 - """ - return bleach.clean( - html, - tags=ALLOWED_TAGS, - attributes=ALLOWED_ATTRIBUTES, - protocols=ALLOWED_PROTOCOLS, # 限制允许的协议 - strip=True, # 移除不允许的标签而不是转义 - strip_comments=True # 移除HTML注释 - ) diff --git a/src/DjangoBlog/djangoblog/whoosh_cn_backend.py b/src/DjangoBlog/djangoblog/whoosh_cn_backend.py deleted file mode 100644 index 04e3f7f..0000000 --- a/src/DjangoBlog/djangoblog/whoosh_cn_backend.py +++ /dev/null @@ -1,1044 +0,0 @@ -# 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/DjangoBlog/djangoblog/wsgi.py b/src/DjangoBlog/djangoblog/wsgi.py deleted file mode 100644 index 2295efd..0000000 --- a/src/DjangoBlog/djangoblog/wsgi.py +++ /dev/null @@ -1,16 +0,0 @@ -""" -WSGI config for djangoblog project. - -It exposes the WSGI callable as a module-level variable named ``application``. - -For more information on this file, see -https://docs.djangoproject.com/en/1.10/howto/deployment/wsgi/ -""" - -import os - -from django.core.wsgi import get_wsgi_application - -os.environ.setdefault("DJANGO_SETTINGS_MODULE", "djangoblog.settings") - -application = get_wsgi_application() diff --git a/src/DjangoBlog/docs/README-en.md b/src/DjangoBlog/docs/README-en.md deleted file mode 100644 index 37ea069..0000000 --- a/src/DjangoBlog/docs/README-en.md +++ /dev/null @@ -1,158 +0,0 @@ -# DjangoBlog - -

- Django CI - CodeQL - codecov - license -

- -

- A powerful, elegant, and modern blog system. -
- English简体中文 -

- ---- - -DjangoBlog is a high-performance blog platform built with Python 3.10 and Django 4.0. It not only provides all the core functionalities of a traditional blog but also features a flexible plugin system, allowing you to easily extend and customize your website. Whether you are a personal blogger, a tech enthusiast, or a content creator, DjangoBlog aims to provide a stable, efficient, and easy-to-maintain environment for writing and publishing. - -## ✨ Features - -- **Powerful Content Management**: Full support for managing articles, standalone pages, categories, and tags. Comes with a powerful built-in Markdown editor with syntax highlighting. -- **Full-Text Search**: Integrated search engine for fast and accurate content searching. -- **Interactive Comment System**: Supports replies, email notifications, and Markdown formatting in comments. -- **Flexible Sidebar**: Customizable modules for displaying recent articles, most viewed posts, tag cloud, and more. -- **Social Login**: Built-in OAuth support, with integrations for Google, GitHub, Facebook, Weibo, QQ, and other major platforms. -- **High-Performance Caching**: Native support for Redis caching with an automatic refresh mechanism to ensure high-speed website responses. -- **SEO Friendly**: Basic SEO features are included, with automatic notifications to Google and Baidu upon new content publication. -- **Extensible Plugin System**: Extend blog functionalities by creating standalone plugins, ensuring decoupled and maintainable code. We have already implemented features like view counting and SEO optimization through plugins! -- **Integrated Image Hosting**: A simple, built-in image hosting feature for easy uploads and management. -- **Automated Frontend**: Integrated with `django-compressor` to automatically compress and optimize CSS and JavaScript files. -- **Robust Operations**: Built-in email notifications for website exceptions and management capabilities through a WeChat Official Account. - -## 🛠️ Tech Stack - -- **Backend**: Python 3.10, Django 4.0 -- **Database**: MySQL, SQLite (configurable) -- **Cache**: Redis -- **Frontend**: HTML5, CSS3, JavaScript -- **Search**: Whoosh, Elasticsearch (configurable) -- **Editor**: Markdown (mdeditor) - -## 🚀 Getting Started - -### 1. Prerequisites - -Ensure you have Python 3.10+ and MySQL/MariaDB installed on your system. - -### 2. Clone & Installation - -```bash -# Clone the project to your local machine -git clone https://github.com/liangliangyy/DjangoBlog.git -cd DjangoBlog - -# Install dependencies -pip install -r requirements.txt -``` - -### 3. Project Configuration - -- **Database**: - Open `djangoblog/settings.py`, locate the `DATABASES` section, and update it with your MySQL connection details. - - ```python - DATABASES = { - 'default': { - 'ENGINE': 'django.db.backends.mysql', - 'NAME': 'djangoblog', - 'USER': 'root', - 'PASSWORD': 'your_password', - 'HOST': '127.0.0.1', - 'PORT': 3306, - } - } - ``` - Create the database in MySQL: - ```sql - CREATE DATABASE `djangoblog` DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; - ``` - -- **More Configurations**: - For advanced settings such as email, OAuth, caching, and more, please refer to our [Detailed Configuration Guide](/docs/config-en.md). - -### 4. Database Initialization - -```bash -python manage.py makemigrations -python manage.py migrate - -# Create a superuser account -python manage.py createsuperuser -``` - -### 5. Running the Project - -```bash -# (Optional) Generate some test data -python manage.py create_testdata - -# (Optional) Collect and compress static files -python manage.py collectstatic --noinput -python manage.py compress --force - -# Start the development server -python manage.py runserver -``` - -Now, open your browser and navigate to `http://127.0.0.1:8000/`. You should see the DjangoBlog homepage! - -## Deployment - -- **Traditional Deployment**: A detailed guide for server deployment is available here: [Deployment Tutorial](https://www.lylinux.net/article/2019/8/5/58.html) (in Chinese). -- **Docker Deployment**: This project fully supports Docker. If you are familiar with containerization, please refer to the [Docker Deployment Guide](/docs/docker-en.md) for a quick start. -- **Kubernetes Deployment**: We also provide a complete [Kubernetes Deployment Guide](/docs/k8s-en.md) to help you go cloud-native easily. - -## 🧩 Plugin System - -The plugin system is a core feature of DjangoBlog. It allows you to add new functionalities to your blog without modifying the core codebase by writing standalone plugins. - -- **How it Works**: Plugins operate by registering callback functions to predefined "hooks". For instance, when an article is rendered, the `after_article_body_get` hook is triggered, and all functions registered to this hook are executed. -- **Existing Plugins**: Features like `view_count` and `seo_optimizer` are implemented through this plugin system. -- **Develop Your Own Plugin**: Simply create a new folder under the `plugins` directory and write your `plugin.py`. We welcome you to explore and contribute your creative ideas to the DjangoBlog community! - -## 🤝 Contributing - -We warmly welcome contributions of any kind! If you have great ideas or have found a bug, please feel free to open an issue or submit a pull request. - -## 📄 License - -This project is open-sourced under the [MIT License](LICENSE). - ---- - -## ❤️ Support & Sponsorship - -If you find this project helpful and wish to support its continued maintenance and development, please consider buying me a coffee! Your support is my greatest motivation. - -

- Alipay Sponsorship - WeChat Sponsorship -

-

- (Left) Alipay / (Right) WeChat -

- -## 🙏 Acknowledgements - -A special thanks to **JetBrains** for providing a free open-source license for this project. - -

- - JetBrains Logo - -

- ---- -> If this project has helped you, please leave your website URL [here](https://github.com/liangliangyy/DjangoBlog/issues/214) to let more people see it. Your feedback is the driving force for my continued updates and maintenance. diff --git a/src/DjangoBlog/docs/config-en.md b/src/DjangoBlog/docs/config-en.md deleted file mode 100644 index b877efb..0000000 --- a/src/DjangoBlog/docs/config-en.md +++ /dev/null @@ -1,64 +0,0 @@ -# Introduction to main features settings - -## Cache: -Cache using `memcache` for default. If you don't have `memcache` environment, you can remove the `default` setting in `CACHES` and change `locmemcache` to `default`. -```python -CACHES = { - 'default': { - 'BACKEND': 'django.core.cache.backends.memcached.MemcachedCache', - 'LOCATION': '127.0.0.1:11211', - 'KEY_PREFIX': 'django_test' if TESTING else 'djangoblog', - 'TIMEOUT': 60 * 60 * 10 - }, - 'locmemcache': { - 'BACKEND': 'django.core.cache.backends.locmem.LocMemCache', - 'TIMEOUT': 10800, - 'LOCATION': 'unique-snowflake', - } -} -``` - -## OAuth Login: -QQ, Weibo, Google, GitHub and Facebook are now supported for OAuth login. Fetch OAuth login permissions from the corresponding open platform, and save them with `appkey`, `appsecret` and callback address in **Backend->OAuth** configuration. - -### Callback address examples: -QQ: http://your-domain-name/oauth/authorize?type=qq -Weibo: http://your-domain-name/oauth/authorize?type=weibo -type is in the type field of `oauthmanager`. - -## owntracks: -owntracks is a location tracking application. It will send your locaiton to the server by timing.Simple support owntracks features. Just install owntracks app and set api address as `your-domain-name/owntracks/logtracks`. Visit `your-domain-name/owntracks/show_dates` and you will see the date with latitude and langitude, click it and see the motion track. The map is drawn by AMap. - -## Email feature: -Same as before, Configure your own error msg recvie email information with`ADMINS = [('liangliang', 'liangliangyy@gmail.com')]` in `settings.py`. And modify: -```python -EMAIL_HOST = 'smtp.zoho.com' -EMAIL_PORT = 587 -EMAIL_HOST_USER = os.environ.get('DJANGO_EMAIL_USER') -EMAIL_HOST_PASSWORD = os.environ.get('DJANGO_EMAIL_PASSWORD') -DEFAULT_FROM_EMAIL = EMAIL_HOST_USER -SERVER_EMAIL = os.environ.get('DJANGO_EMAIL_USER') -``` -with your email account information. - -## WeChat Official Account -Simple wechat official account features integrated. Set token as `your-domain-name/robot` in wechat backend. Default token is `lylinux`, you can change it to your own in `servermanager/robot.py`. Add a new command in `Backend->Servermanager->command`, in this way, you can manage the system through wechat official account. - -## Introduction to website configuration -You can add website configuration in **Backend->BLOG->WebSiteConfiguration**. Such as: keywords, description, Google Ad, website stats code, case number, etc. -OAuth user avatar path is saved in *StaticFileSavedAddress*. Please input absolute path, code directory for default. - -## Source code highlighting -If the code block in your article didn't show hightlight, please write the code blocks as following: - -![](https://resource.lylinux.net/image/codelang.png) - -That is, you should add the corresponding language name before the code block. - -## Update -If you get errors as following while executing database migrations: -```python -django.db.migrations.exceptions.MigrationSchemaMissing: Unable to create the django_migrations table ((1064, "You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near '(6) NOT NULL)' at line 1")) -``` -This problem may cause by the mysql version under 5.6, a new version( >= 5.6 ) mysql is needed. - diff --git a/src/DjangoBlog/docs/config.md b/src/DjangoBlog/docs/config.md deleted file mode 100644 index 24673a3..0000000 --- a/src/DjangoBlog/docs/config.md +++ /dev/null @@ -1,58 +0,0 @@ -# 主要功能配置介绍: - -## 缓存: -缓存默认使用`localmem`缓存,如果你有`redis`环境,可以设置`DJANGO_REDIS_URL`环境变量,则会自动使用该redis来作为缓存,或者你也可以直接修改如下代码来使用。 -https://github.com/liangliangyy/DjangoBlog/blob/ffcb2c3711de805f2067dd3c1c57449cd24d84ee/djangoblog/settings.py#L185-L199 - - -## oauth登录: - -现在已经支持QQ,微博,Google,GitHub,Facebook登录,需要在其对应的开放平台申请oauth登录权限,然后在 -**后台->Oauth** 配置中新增配置,填写对应的`appkey`和`appsecret`以及回调地址。 -### 回调地址示例: -qq:http://你的域名/oauth/authorize?type=qq -微博:http://你的域名/oauth/authorize?type=weibo -type对应在`oauthmanager`中的type字段。 - -## owntracks: -owntracks是一个位置追踪软件,可以定时的将你的坐标提交到你的服务器上,现在简单的支持owntracks功能,需要安装owntracks的app,然后将api地址设置为: -`你的域名/owntracks/logtracks`就可以了。然后访问`你的域名/owntracks/show_dates`就可以看到有经纬度记录的日期,点击之后就可以看到运动轨迹了。地图是使用高德地图绘制。 - -## 邮件功能: -同样,将`settings.py`中的`ADMINS = [('liangliang', 'liangliangyy@gmail.com')]`配置为你自己的错误接收邮箱,另外修改: -```python -EMAIL_HOST = 'smtp.zoho.com' -EMAIL_PORT = 587 -EMAIL_HOST_USER = os.environ.get('DJANGO_EMAIL_USER') -EMAIL_HOST_PASSWORD = os.environ.get('DJANGO_EMAIL_PASSWORD') -DEFAULT_FROM_EMAIL = EMAIL_HOST_USER -SERVER_EMAIL = os.environ.get('DJANGO_EMAIL_USER') -``` -为你自己的邮箱配置。 - -## 微信公众号 -集成了简单的微信公众号功能,在微信后台将token地址设置为:`你的域名/robot` 即可,默认token为`lylinux`,当然你可以修改为你自己的,在`servermanager/robot.py`中。 -然后在**后台->Servermanager->命令**中新增命令,这样就可以使用微信公众号来管理了。 -## 网站配置介绍 -在**后台->BLOG->网站配置**中,可以新增网站配置,比如关键字,描述等,以及谷歌广告,网站统计代码及备案号等等。 -其中的*静态文件保存地址*是保存oauth用户登录的头像路径,填写绝对路径,默认是代码目录。 -## 代码高亮 -如果你发现你文章的代码没有高亮,请这样书写代码块: - -![](https://resource.lylinux.net/image/codelang.png) - - -也就是说,需要在代码块开始位置加入这段代码对应的语言。 - -## update -如果你发现执行数据库迁移的时候出现如下报错: -```python -django.db.migrations.exceptions.MigrationSchemaMissing: Unable to create the django_migrations table ((1064, "You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near '(6) NOT NULL)' at line 1")) -``` -可能是因为你的mysql版本低于5.6,需要升级mysql版本>=5.6即可。 - - -django 4.0登录可能会报错CSRF,需要配置下`settings.py`中的`CSRF_TRUSTED_ORIGINS` - -https://github.com/liangliangyy/DjangoBlog/blob/master/djangoblog/settings.py#L39 - diff --git a/src/DjangoBlog/docs/docker-en.md b/src/DjangoBlog/docs/docker-en.md deleted file mode 100644 index 8d5d59e..0000000 --- a/src/DjangoBlog/docs/docker-en.md +++ /dev/null @@ -1,114 +0,0 @@ -# Deploying DjangoBlog with Docker - -![Docker Pulls](https://img.shields.io/docker/pulls/liangliangyy/djangoblog) -![Docker Image Version (latest by date)](https://img.shields.io/docker/v/liangliangyy/djangoblog?sort=date) -![Docker Image Size (latest by date)](https://img.shields.io/docker/image-size/liangliangyy/djangoblog) - -This project fully supports containerized deployment using Docker, providing you with a fast, consistent, and isolated runtime environment. We recommend using `docker-compose` to launch the entire blog service stack with a single command. - -## 1. Prerequisites - -Before you begin, please ensure you have the following software installed on your system: -- [Docker Engine](https://docs.docker.com/engine/install/) -- [Docker Compose](https://docs.docker.com/compose/install/) (Included with Docker Desktop for Mac and Windows) - -## 2. Recommended Method: Using `docker-compose` (One-Click Deployment) - -This is the simplest and most recommended way to deploy. It automatically creates and manages the Django application, a MySQL database, and an optional Elasticsearch service for you. - -### Step 1: Start the Basic Services - -From the project's root directory, run the following command: - -```bash -# Build and start the containers in detached mode (includes Django app and MySQL) -docker-compose up -d --build -``` - -`docker-compose` will read the `docker-compose.yml` file, pull the necessary images, build the project image, and start all services. - -- **Access Your Blog**: Once the services are up, you can access the blog by navigating to `http://127.0.0.1` in your browser. -- **Data Persistence**: MySQL data files will be stored in the `data/mysql` directory within the project root, ensuring that your data persists across container restarts. - -### Step 2: (Optional) Enable Elasticsearch for Full-Text Search - -If you want to use Elasticsearch for more powerful full-text search capabilities, you can include the `docker-compose.es.yml` configuration file: - -```bash -# Build and start all services in detached mode (Django, MySQL, Elasticsearch) -docker-compose -f docker-compose.yml -f deploy/docker-compose/docker-compose.es.yml up -d --build -``` -- **Data Persistence**: Elasticsearch data will be stored in the `data/elasticsearch` directory. - -### Step 3: First-Time Initialization - -After the containers start for the first time, you'll need to execute some initialization commands inside the application container. - -```bash -# Get a shell inside the djangoblog application container (named 'web') -docker-compose exec web bash - -# Inside the container, run the following commands: -# Create a superuser account (follow the prompts to set username, email, and password) -python manage.py createsuperuser - -# (Optional) Create some test data -python manage.py create_testdata - -# (Optional, if ES is enabled) Create the search index -python manage.py rebuild_index - -# Exit the container -exit -``` - -## 3. Alternative Method: Using the Standalone Docker Image - -If you already have an external MySQL database running, you can run the DjangoBlog application image by itself. - -```bash -# Pull the latest image from Docker Hub -docker pull liangliangyy/djangoblog:latest - -# Run the container and connect it to your external database -docker run -d \ - -p 8000:8000 \ - -e DJANGO_SECRET_KEY='your-strong-secret-key' \ - -e DJANGO_MYSQL_HOST='your-mysql-host' \ - -e DJANGO_MYSQL_USER='your-mysql-user' \ - -e DJANGO_MYSQL_PASSWORD='your-mysql-password' \ - -e DJANGO_MYSQL_DATABASE='djangoblog' \ - --name djangoblog \ - liangliangyy/djangoblog:latest -``` - -- **Access Your Blog**: After startup, visit `http://127.0.0.1:8000`. -- **Create Superuser**: `docker exec -it djangoblog python manage.py createsuperuser` - -## 4. Configuration (Environment Variables) - -Most of the project's configuration is managed through environment variables. You can modify them in the `docker-compose.yml` file or pass them using the `-e` flag with the `docker run` command. - -| Environment Variable | Default/Example Value | Notes | -|---------------------------|--------------------------------------------------------------------------|---------------------------------------------------------------------| -| `DJANGO_SECRET_KEY` | `your-strong-secret-key` | **Must be changed to a random, complex string!** | -| `DJANGO_DEBUG` | `False` | Toggles Django's debug mode. | -| `DJANGO_MYSQL_HOST` | `mysql` | Database hostname. | -| `DJANGO_MYSQL_PORT` | `3306` | Database port. | -| `DJANGO_MYSQL_DATABASE` | `djangoblog` | Database name. | -| `DJANGO_MYSQL_USER` | `root` | Database username. | -| `DJANGO_MYSQL_PASSWORD` | `djangoblog_123` | Database password. | -| `DJANGO_REDIS_URL` | `redis:6379/0` | Redis connection URL (for caching). | -| `DJANGO_ELASTICSEARCH_HOST`| `elasticsearch:9200` | Elasticsearch host address. | -| `DJANGO_EMAIL_HOST` | `smtp.example.org` | Email server address. | -| `DJANGO_EMAIL_PORT` | `465` | Email server port. | -| `DJANGO_EMAIL_USER` | `user@example.org` | Email account username. | -| `DJANGO_EMAIL_PASSWORD` | `your-email-password` | Email account password. | -| `DJANGO_EMAIL_USE_SSL` | `True` | Whether to use SSL. | -| `DJANGO_EMAIL_USE_TLS` | `False` | Whether to use TLS. | -| `DJANGO_ADMIN_EMAIL` | `admin@example.org` | Admin email for receiving error reports. | -| `DJANGO_BAIDU_NOTIFY_URL` | `http://data.zz.baidu.com/...` | Push API from [Baidu Webmaster Tools](https://ziyuan.baidu.com/linksubmit/index). | - ---- - -After deployment, please review and adjust these environment variables according to your needs, especially `DJANGO_SECRET_KEY` and the database and email settings. \ No newline at end of file diff --git a/src/DjangoBlog/docs/docker.md b/src/DjangoBlog/docs/docker.md deleted file mode 100644 index e7c255a..0000000 --- a/src/DjangoBlog/docs/docker.md +++ /dev/null @@ -1,114 +0,0 @@ -# 使用 Docker 部署 DjangoBlog - -![Docker Pulls](https://img.shields.io/docker/pulls/liangliangyy/djangoblog) -![Docker Image Version (latest by date)](https://img.shields.io/docker/v/liangliangyy/djangoblog?sort=date) -![Docker Image Size (latest by date)](https://img.shields.io/docker/image-size/liangliangyy/djangoblog) - -本项目全面支持使用 Docker 进行容器化部署,为您提供了快速、一致且隔离的运行环境。我们推荐使用 `docker-compose` 来一键启动整个博客服务栈。 - -## 1. 环境准备 - -在开始之前,请确保您的系统中已经安装了以下软件: -- [Docker Engine](https://docs.docker.com/engine/install/) -- [Docker Compose](https://docs.docker.com/compose/install/) (对于 Docker Desktop 用户,它已内置) - -## 2. 推荐方式:使用 `docker-compose` (一键部署) - -这是最简单、最推荐的部署方式。它会自动为您创建并管理 Django 应用、MySQL 数据库,以及可选的 Elasticsearch 服务。 - -### 步骤 1: 启动基础服务 - -在项目根目录下,执行以下命令: - -```bash -# 构建并以后台模式启动容器 (包含 Django 应用和 MySQL) -docker-compose up -d --build -``` - -`docker-compose` 会读取 `docker-compose.yml` 文件,自动拉取所需镜像、构建项目镜像,并启动所有服务。 - -- **访问您的博客**: 服务启动后,在浏览器中访问 `http://127.0.0.1` 即可看到博客首页。 -- **数据持久化**: MySQL 的数据文件将存储在项目根目录下的 `data/mysql` 文件夹中,确保数据在容器重启后不丢失。 - -### 步骤 2: (可选) 启用 Elasticsearch 全文搜索 - -如果您希望使用 Elasticsearch 提供更强大的全文搜索功能,可以额外加载 `docker-compose.es.yml` 配置文件: - -```bash -# 构建并以后台模式启动所有服务 (Django, MySQL, Elasticsearch) -docker-compose -f docker-compose.yml -f deploy/docker-compose/docker-compose.es.yml up -d --build -``` -- **数据持久化**: Elasticsearch 的数据将存储在 `data/elasticsearch` 文件夹中。 - -### 步骤 3: 首次运行的初始化操作 - -当容器首次启动后,您需要进入容器来执行一些初始化命令。 - -```bash -# 进入 djangoblog 应用容器 -docker-compose exec web bash - -# 在容器内执行以下命令: -# 创建超级管理员账户 (请按照提示设置用户名、邮箱和密码) -python manage.py createsuperuser - -# (可选) 创建一些测试数据 -python manage.py create_testdata - -# (可选,如果启用了 ES) 创建索引 -python manage.py rebuild_index - -# 退出容器 -exit -``` - -## 3. 备选方式:使用独立的 Docker 镜像 - -如果您已经拥有一个正在运行的外部 MySQL 数据库,您也可以只运行 DjangoBlog 的应用镜像。 - -```bash -# 从 Docker Hub 拉取最新镜像 -docker pull liangliangyy/djangoblog:latest - -# 运行容器,并链接到您的外部数据库 -docker run -d \ - -p 8000:8000 \ - -e DJANGO_SECRET_KEY='your-strong-secret-key' \ - -e DJANGO_MYSQL_HOST='your-mysql-host' \ - -e DJANGO_MYSQL_USER='your-mysql-user' \ - -e DJANGO_MYSQL_PASSWORD='your-mysql-password' \ - -e DJANGO_MYSQL_DATABASE='djangoblog' \ - --name djangoblog \ - liangliangyy/djangoblog:latest -``` - -- **访问您的博客**: 启动完成后,访问 `http://127.0.0.1:8000`。 -- **创建管理员**: `docker exec -it djangoblog python manage.py createsuperuser` - -## 4. 配置说明 (环境变量) - -本项目的大部分配置都通过环境变量来管理。您可以在 `docker-compose.yml` 文件中修改它们,或者在使用 `docker run` 命令时通过 `-e` 参数传入。 - -| 环境变量名称 | 默认值/示例 | 备注 | -|-------------------------|--------------------------------------------------------------------------|---------------------------------------------------------------------| -| `DJANGO_SECRET_KEY` | `your-strong-secret-key` | **请务必修改为一个随机且复杂的字符串!** | -| `DJANGO_DEBUG` | `False` | 是否开启 Django 的调试模式 | -| `DJANGO_MYSQL_HOST` | `mysql` | 数据库主机名 | -| `DJANGO_MYSQL_PORT` | `3306` | 数据库端口 | -| `DJANGO_MYSQL_DATABASE` | `djangoblog` | 数据库名称 | -| `DJANGO_MYSQL_USER` | `root` | 数据库用户名 | -| `DJANGO_MYSQL_PASSWORD` | `djangoblog_123` | 数据库密码 | -| `DJANGO_REDIS_URL` | `redis:6379/0` | Redis 连接地址 (用于缓存) | -| `DJANGO_ELASTICSEARCH_HOST` | `elasticsearch:9200` | Elasticsearch 主机地址 | -| `DJANGO_EMAIL_HOST` | `smtp.example.org` | 邮件服务器地址 | -| `DJANGO_EMAIL_PORT` | `465` | 邮件服务器端口 | -| `DJANGO_EMAIL_USER` | `user@example.org` | 邮件账户 | -| `DJANGO_EMAIL_PASSWORD` | `your-email-password` | 邮件密码 | -| `DJANGO_EMAIL_USE_SSL` | `True` | 是否使用 SSL | -| `DJANGO_EMAIL_USE_TLS` | `False` | 是否使用 TLS | -| `DJANGO_ADMIN_EMAIL` | `admin@example.org` | 接收异常报告的管理员邮箱 | -| `DJANGO_BAIDU_NOTIFY_URL` | `http://data.zz.baidu.com/...` | [百度站长平台](https://ziyuan.baidu.com/linksubmit/index) 的推送接口 | - ---- - -部署完成后,请务必检查并根据您的实际需求调整这些环境变量,特别是 `DJANGO_SECRET_KEY` 和数据库、邮件相关的配置。 diff --git a/src/DjangoBlog/docs/es.md b/src/DjangoBlog/docs/es.md deleted file mode 100644 index 97226c5..0000000 --- a/src/DjangoBlog/docs/es.md +++ /dev/null @@ -1,28 +0,0 @@ -# 集成Elasticsearch -如果你已经有了`Elasticsearch`环境,那么可以将搜索从`Whoosh`换成`Elasticsearch`,集成方式也很简单, -首先需要注意如下几点: -1. 你的`Elasticsearch`支持`ik`中文分词 -2. 你的`Elasticsearch`版本>=7.3.0 - -接下来在`settings.py`做如下改动即可: -- 增加es链接,如下所示: -```python -ELASTICSEARCH_DSL = { - 'default': { - 'hosts': '127.0.0.1:9200' - }, -} -``` -- 修改`HAYSTACK`配置: -```python -HAYSTACK_CONNECTIONS = { - 'default': { - 'ENGINE': 'djangoblog.elasticsearch_backend.ElasticSearchEngine', - }, -} -``` -然后终端执行: -```shell script -./manage.py build_index -``` -这将会在你的es中创建两个索引,分别是`blog`和`performance`,其中`blog`索引就是搜索所使用的,而`performance`会记录每个请求的响应时间,以供将来优化使用。 \ No newline at end of file diff --git a/src/DjangoBlog/docs/imgs/alipay.jpg b/src/DjangoBlog/docs/imgs/alipay.jpg deleted file mode 100644 index 424d70a..0000000 Binary files a/src/DjangoBlog/docs/imgs/alipay.jpg and /dev/null differ diff --git a/src/DjangoBlog/docs/imgs/pycharm_logo.png b/src/DjangoBlog/docs/imgs/pycharm_logo.png deleted file mode 100644 index 7f2a4b0..0000000 Binary files a/src/DjangoBlog/docs/imgs/pycharm_logo.png and /dev/null differ diff --git a/src/DjangoBlog/docs/imgs/wechat.jpg b/src/DjangoBlog/docs/imgs/wechat.jpg deleted file mode 100644 index 7edf525..0000000 Binary files a/src/DjangoBlog/docs/imgs/wechat.jpg and /dev/null differ diff --git a/src/DjangoBlog/docs/k8s-en.md b/src/DjangoBlog/docs/k8s-en.md deleted file mode 100644 index 20e9527..0000000 --- a/src/DjangoBlog/docs/k8s-en.md +++ /dev/null @@ -1,141 +0,0 @@ -# Deploying DjangoBlog with Kubernetes - -This document guides you through deploying the DjangoBlog application on a Kubernetes (K8s) cluster. We provide a complete set of `.yaml` configuration files in the `deploy/k8s` directory to deploy a full service stack, including the DjangoBlog application, Nginx, MySQL, Redis, and Elasticsearch. - -## Architecture Overview - -This deployment utilizes a microservices-based, cloud-native architecture: - -- **Core Components**: Each core service (DjangoBlog, Nginx, MySQL, Redis, Elasticsearch) runs as a separate `Deployment`. -- **Configuration Management**: Nginx configurations and Django application environment variables are managed via `ConfigMap`. **Note: For sensitive information like passwords, using `Secret` is highly recommended.** -- **Service Discovery**: All services are exposed internally within the cluster as `ClusterIP` type `Service`, enabling communication via service names. -- **External Access**: An `Ingress` resource is used to route external HTTP traffic to the Nginx service, which acts as the single entry point for the entire blog application. -- **Data Persistence**: A `local-storage` solution based on node-local paths is used. This requires you to manually create storage directories on a specific K8s node and statically bind them using `PersistentVolume` (PV) and `PersistentVolumeClaim` (PVC). - -## 1. Prerequisites - -Before you begin, please ensure you have the following: - -- A running Kubernetes cluster. -- The `kubectl` command-line tool configured to connect to your cluster. -- An [Nginx Ingress Controller](https://kubernetes.github.io/ingress-nginx/deploy/) installed and configured in your cluster. -- Filesystem access to one of the nodes in your cluster (defaulted to `master` in the configs) to create local storage directories. - -## 2. Deployment Steps - -### Step 1: Create a Namespace - -We recommend deploying all DjangoBlog-related resources in a dedicated namespace for better management. - -```bash -# Create a namespace named 'djangoblog' -kubectl create namespace djangoblog -``` - -### Step 2: Configure Persistent Storage - -This setup uses Local Persistent Volumes. You need to create the data storage directories on a node within your cluster (the default is the `master` node in `pv.yaml`). - -```bash -# Log in to your master node -ssh user@master-node - -# Create the required storage directories -sudo mkdir -p /mnt/local-storage-db -sudo mkdir -p /mnt/local-storage-djangoblog -sudo mkdir -p /mnt/resource/ -sudo mkdir -p /mnt/local-storage-elasticsearch - -# Log out from the node -exit -``` -**Note**: If you wish to store data on a different node or use different paths, you must modify the `nodeAffinity` and `local.path` settings in the `deploy/k8s/pv.yaml` file. - -After creating the directories, apply the storage-related configurations: - -```bash -# Apply the StorageClass -kubectl apply -f deploy/k8s/storageclass.yaml - -# Apply the PersistentVolumes (PVs) -kubectl apply -f deploy/k8s/pv.yaml - -# Apply the PersistentVolumeClaims (PVCs) -kubectl apply -f deploy/k8s/pvc.yaml -``` - -### Step 3: Configure the Application - -Before deploying the application, you need to edit the `deploy/k8s/configmap.yaml` file to modify sensitive information and custom settings. - -**It is strongly recommended to change the following fields:** -- `DJANGO_SECRET_KEY`: Change to a random, complex string. -- `DJANGO_MYSQL_PASSWORD` and `MYSQL_ROOT_PASSWORD`: Change to your own secure database password. - -```bash -# Edit the ConfigMap file -vim deploy/k8s/configmap.yaml - -# Apply the configuration -kubectl apply -f deploy/k8s/configmap.yaml -``` - -### Step 4: Deploy the Application Stack - -Now, we can deploy all the core services. - -```bash -# Deploy the Deployments (DjangoBlog, MySQL, Redis, Nginx, ES) -kubectl apply -f deploy/k8s/deployment.yaml - -# Deploy the Services (to create internal endpoints for the Deployments) -kubectl apply -f deploy/k8s/service.yaml -``` - -The deployment may take some time. You can run the following command to check if all Pods are running successfully (STATUS should be `Running`): - -```bash -kubectl get pods -n djangoblog -w -``` - -### Step 5: Expose the Application Externally - -Finally, expose the Nginx service to external traffic by applying the `Ingress` rule. - -```bash -# Apply the Ingress rule -kubectl apply -f deploy/k8s/gateway.yaml -``` - -Once deployed, you can access your blog via the external IP address of your Ingress Controller. Use the following command to find the address: - -```bash -kubectl get ingress -n djangoblog -``` - -### Step 6: First-Time Initialization - -Similar to the Docker deployment, you need to get a shell into the DjangoBlog application Pod to perform database initialization and create a superuser on the first run. - -```bash -# First, get the name of a djangoblog pod -kubectl get pods -n djangoblog | grep djangoblog - -# Exec into one of the Pods (replace [pod-name] with the name from the previous step) -kubectl exec -it [pod-name] -n djangoblog -- bash - -# Inside the Pod, run the following commands: -# Create a superuser account (follow the prompts) -python manage.py createsuperuser - -# (Optional) Create some test data -python manage.py create_testdata - -# (Optional, if ES is enabled) Create the search index -python manage.py rebuild_index - -# Exit the Pod -exit -``` - -Congratulations! You have successfully deployed DjangoBlog on your Kubernetes cluster. \ No newline at end of file diff --git a/src/DjangoBlog/docs/k8s.md b/src/DjangoBlog/docs/k8s.md deleted file mode 100644 index 9da3c28..0000000 --- a/src/DjangoBlog/docs/k8s.md +++ /dev/null @@ -1,141 +0,0 @@ -# 使用 Kubernetes 部署 DjangoBlog - -本文档将指导您如何在 Kubernetes (K8s) 集群上部署 DjangoBlog 应用。我们提供了一套完整的 `.yaml` 配置文件,位于 `deploy/k8s` 目录下,用于部署一个包含 DjangoBlog 应用、Nginx、MySQL、Redis 和 Elasticsearch 的完整服务栈。 - -## 架构概览 - -本次部署采用的是微服务化的云原生架构: - -- **核心组件**: 每个核心服务 (DjangoBlog, Nginx, MySQL, Redis, Elasticsearch) 都将作为独立的 `Deployment` 运行。 -- **配置管理**: Nginx 的配置文件和 Django 应用的环境变量通过 `ConfigMap` 进行管理。**注意:敏感信息(如密码)建议使用 `Secret` 进行管理。** -- **服务发现**: 所有服务都通过 `ClusterIP` 类型的 `Service` 在集群内部暴露,并通过服务名相互通信。 -- **外部访问**: 使用 `Ingress` 资源将外部的 HTTP 流量路由到 Nginx 服务,作为整个博客应用的统一入口。 -- **数据持久化**: 采用基于节点本地路径的 `local-storage` 方案。这需要您在指定的 K8s 节点上手动创建存储目录,并通过 `PersistentVolume` (PV) 和 `PersistentVolumeClaim` (PVC) 进行静态绑定。 - -## 1. 环境准备 - -在开始之前,请确保您已具备以下环境: - -- 一个正在运行的 Kubernetes 集群。 -- `kubectl` 命令行工具已配置并能够连接到您的集群。 -- 集群中已安装并配置好 [Nginx Ingress Controller](https://kubernetes.github.io/ingress-nginx/deploy/)。 -- 对集群中的一个节点(默认为 `master`)拥有文件系统访问权限,用于创建本地存储目录。 - -## 2. 部署步骤 - -### 步骤 1: 创建命名空间 - -我们建议将 DjangoBlog 相关的所有资源都部署在一个独立的命名空间中,便于管理。 - -```bash -# 创建一个名为 djangoblog 的命名空间 -kubectl create namespace djangoblog -``` - -### 步骤 2: 配置持久化存储 - -此方案使用本地持久卷 (Local Persistent Volume)。您需要在集群的一个节点上(在 `pv.yaml` 文件中默认为 `master` 节点)创建用于数据存储的目录。 - -```bash -# 登录到您的 master 节点 -ssh user@master-node - -# 创建所需的存储目录 -sudo mkdir -p /mnt/local-storage-db -sudo mkdir -p /mnt/local-storage-djangoblog -sudo mkdir -p /mnt/resource/ -sudo mkdir -p /mnt/local-storage-elasticsearch - -# 退出节点 -exit -``` -**注意**: 如果您希望将数据存储在其他节点或使用不同的路径,请务必修改 `deploy/k8s/pv.yaml` 文件中 `nodeAffinity` 和 `local.path` 的配置。 - -创建目录后,应用存储相关的配置文件: - -```bash -# 应用 StorageClass -kubectl apply -f deploy/k8s/storageclass.yaml - -# 应用 PersistentVolume (PV) -kubectl apply -f deploy/k8s/pv.yaml - -# 应用 PersistentVolumeClaim (PVC) -kubectl apply -f deploy/k8s/pvc.yaml -``` - -### 步骤 3: 配置应用 - -在部署应用之前,您需要编辑 `deploy/k8s/configmap.yaml` 文件,修改其中的敏感信息和个性化配置。 - -**强烈建议修改以下字段:** -- `DJANGO_SECRET_KEY`: 修改为一个随机且复杂的字符串。 -- `DJANGO_MYSQL_PASSWORD` 和 `MYSQL_ROOT_PASSWORD`: 修改为您自己的数据库密码。 - -```bash -# 编辑 ConfigMap 文件 -vim deploy/k8s/configmap.yaml - -# 应用配置 -kubectl apply -f deploy/k8s/configmap.yaml -``` - -### 步骤 4: 部署应用服务栈 - -现在,我们可以部署所有的核心服务了。 - -```bash -# 部署 Deployments (DjangoBlog, MySQL, Redis, Nginx, ES) -kubectl apply -f deploy/k8s/deployment.yaml - -# 部署 Services (为 Deployments 创建内部访问端点) -kubectl apply -f deploy/k8s/service.yaml -``` - -部署需要一些时间,您可以运行以下命令检查所有 Pod 是否都已成功运行 (STATUS 为 `Running`): - -```bash -kubectl get pods -n djangoblog -w -``` - -### 步骤 5: 暴露应用到外部 - -最后,通过应用 `Ingress` 规则来将外部流量引导至我们的 Nginx 服务。 - -```bash -# 应用 Ingress 规则 -kubectl apply -f deploy/k8s/gateway.yaml -``` - -部署完成后,您可以通过 Ingress Controller 的外部 IP 地址来访问您的博客。执行以下命令获取地址: - -```bash -kubectl get ingress -n djangoblog -``` - -### 步骤 6: 首次运行的初始化操作 - -与 Docker 部署类似,首次运行时,您需要进入 DjangoBlog 应用的 Pod 来执行数据库初始化和创建管理员账户。 - -```bash -# 首先,获取 djangoblog pod 的名称 -kubectl get pods -n djangoblog | grep djangoblog - -# 进入其中一个 Pod (将 [pod-name] 替换为上一步获取到的名称) -kubectl exec -it [pod-name] -n djangoblog -- bash - -# 在 Pod 内部执行以下命令: -# 创建超级管理员账户 (请按照提示操作) -python manage.py createsuperuser - -# (可选) 创建测试数据 -python manage.py create_testdata - -# (可选,如果启用了 ES) 创建索引 -python manage.py rebuild_index - -# 退出 Pod -exit -``` - -至此,您已成功在 Kubernetes 集群上完成了 DjangoBlog 的部署! \ No newline at end of file diff --git a/src/DjangoBlog/locale/en/LC_MESSAGES/django.mo b/src/DjangoBlog/locale/en/LC_MESSAGES/django.mo deleted file mode 100644 index f63669f..0000000 Binary files a/src/DjangoBlog/locale/en/LC_MESSAGES/django.mo and /dev/null differ diff --git a/src/DjangoBlog/locale/en/LC_MESSAGES/django.po b/src/DjangoBlog/locale/en/LC_MESSAGES/django.po deleted file mode 100644 index c80b30a..0000000 --- a/src/DjangoBlog/locale/en/LC_MESSAGES/django.po +++ /dev/null @@ -1,685 +0,0 @@ -# SOME DESCRIPTIVE TITLE. -# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER -# This file is distributed under the same license as the PACKAGE package. -# FIRST AUTHOR , YEAR. -# -#, fuzzy -msgid "" -msgstr "" -"Project-Id-Version: PACKAGE VERSION\n" -"Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2023-09-13 16:02+0800\n" -"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" -"Last-Translator: FULL NAME \n" -"Language-Team: LANGUAGE \n" -"Language: \n" -"MIME-Version: 1.0\n" -"Content-Type: text/plain; charset=UTF-8\n" -"Content-Transfer-Encoding: 8bit\n" -"Plural-Forms: nplurals=2; plural=(n != 1);\n" - -#: .\accounts\admin.py:12 -msgid "password" -msgstr "password" - -#: .\accounts\admin.py:13 -msgid "Enter password again" -msgstr "Enter password again" - -#: .\accounts\admin.py:24 .\accounts\forms.py:89 -msgid "passwords do not match" -msgstr "passwords do not match" - -#: .\accounts\forms.py:36 -msgid "email already exists" -msgstr "email already exists" - -#: .\accounts\forms.py:46 .\accounts\forms.py:50 -msgid "New password" -msgstr "New password" - -#: .\accounts\forms.py:60 -msgid "Confirm password" -msgstr "Confirm password" - -#: .\accounts\forms.py:70 .\accounts\forms.py:116 -msgid "Email" -msgstr "Email" - -#: .\accounts\forms.py:76 .\accounts\forms.py:80 -msgid "Code" -msgstr "Code" - -#: .\accounts\forms.py:100 .\accounts\tests.py:194 -msgid "email does not exist" -msgstr "email does not exist" - -#: .\accounts\models.py:12 .\oauth\models.py:17 -msgid "nick name" -msgstr "nick name" - -#: .\accounts\models.py:13 .\blog\models.py:29 .\blog\models.py:266 -#: .\blog\models.py:284 .\comments\models.py:13 .\oauth\models.py:23 -#: .\oauth\models.py:53 -msgid "creation time" -msgstr "creation time" - -#: .\accounts\models.py:14 .\comments\models.py:14 .\oauth\models.py:24 -#: .\oauth\models.py:54 -msgid "last modify time" -msgstr "last modify time" - -#: .\accounts\models.py:15 -msgid "create source" -msgstr "create source" - -#: .\accounts\models.py:33 .\djangoblog\logentryadmin.py:81 -msgid "user" -msgstr "user" - -#: .\accounts\tests.py:216 .\accounts\utils.py:39 -msgid "Verification code error" -msgstr "Verification code error" - -#: .\accounts\utils.py:13 -msgid "Verify Email" -msgstr "Verify Email" - -#: .\accounts\utils.py:21 -#, python-format -msgid "" -"You are resetting the password, the verification code is:%(code)s, valid " -"within 5 minutes, please keep it properly" -msgstr "" -"You are resetting the password, the verification code is:%(code)s, valid " -"within 5 minutes, please keep it properly" - -#: .\blog\admin.py:13 .\blog\models.py:92 .\comments\models.py:17 -#: .\oauth\models.py:12 -msgid "author" -msgstr "author" - -#: .\blog\admin.py:53 -msgid "Publish selected articles" -msgstr "Publish selected articles" - -#: .\blog\admin.py:54 -msgid "Draft selected articles" -msgstr "Draft selected articles" - -#: .\blog\admin.py:55 -msgid "Close article comments" -msgstr "Close article comments" - -#: .\blog\admin.py:56 -msgid "Open article comments" -msgstr "Open article comments" - -#: .\blog\admin.py:89 .\blog\models.py:101 .\blog\models.py:183 -#: .\templates\blog\tags\sidebar.html:40 -msgid "category" -msgstr "category" - -#: .\blog\models.py:20 .\blog\models.py:179 .\templates\share_layout\nav.html:8 -msgid "index" -msgstr "index" - -#: .\blog\models.py:21 -msgid "list" -msgstr "list" - -#: .\blog\models.py:22 -msgid "post" -msgstr "post" - -#: .\blog\models.py:23 -msgid "all" -msgstr "all" - -#: .\blog\models.py:24 -msgid "slide" -msgstr "slide" - -#: .\blog\models.py:30 .\blog\models.py:267 .\blog\models.py:285 -msgid "modify time" -msgstr "modify time" - -#: .\blog\models.py:63 -msgid "Draft" -msgstr "Draft" - -#: .\blog\models.py:64 -msgid "Published" -msgstr "Published" - -#: .\blog\models.py:67 -msgid "Open" -msgstr "Open" - -#: .\blog\models.py:68 -msgid "Close" -msgstr "Close" - -#: .\blog\models.py:71 .\comments\admin.py:47 -msgid "Article" -msgstr "Article" - -#: .\blog\models.py:72 -msgid "Page" -msgstr "Page" - -#: .\blog\models.py:74 .\blog\models.py:280 -msgid "title" -msgstr "title" - -#: .\blog\models.py:75 -msgid "body" -msgstr "body" - -#: .\blog\models.py:77 -msgid "publish time" -msgstr "publish time" - -#: .\blog\models.py:79 -msgid "status" -msgstr "status" - -#: .\blog\models.py:84 -msgid "comment status" -msgstr "comment status" - -#: .\blog\models.py:88 .\oauth\models.py:43 -msgid "type" -msgstr "type" - -#: .\blog\models.py:89 -msgid "views" -msgstr "views" - -#: .\blog\models.py:97 .\blog\models.py:258 .\blog\models.py:282 -msgid "order" -msgstr "order" - -#: .\blog\models.py:98 -msgid "show toc" -msgstr "show toc" - -#: .\blog\models.py:105 .\blog\models.py:249 -msgid "tag" -msgstr "tag" - -#: .\blog\models.py:115 .\comments\models.py:21 -msgid "article" -msgstr "article" - -#: .\blog\models.py:171 -msgid "category name" -msgstr "category name" - -#: .\blog\models.py:174 -msgid "parent category" -msgstr "parent category" - -#: .\blog\models.py:234 -msgid "tag name" -msgstr "tag name" - -#: .\blog\models.py:256 -msgid "link name" -msgstr "link name" - -#: .\blog\models.py:257 .\blog\models.py:271 -msgid "link" -msgstr "link" - -#: .\blog\models.py:260 -msgid "is show" -msgstr "is show" - -#: .\blog\models.py:262 -msgid "show type" -msgstr "show type" - -#: .\blog\models.py:281 -msgid "content" -msgstr "content" - -#: .\blog\models.py:283 .\oauth\models.py:52 -msgid "is enable" -msgstr "is enable" - -#: .\blog\models.py:289 -msgid "sidebar" -msgstr "sidebar" - -#: .\blog\models.py:299 -msgid "site name" -msgstr "site name" - -#: .\blog\models.py:305 -msgid "site description" -msgstr "site description" - -#: .\blog\models.py:311 -msgid "site seo description" -msgstr "site seo description" - -#: .\blog\models.py:313 -msgid "site keywords" -msgstr "site keywords" - -#: .\blog\models.py:318 -msgid "article sub length" -msgstr "article sub length" - -#: .\blog\models.py:319 -msgid "sidebar article count" -msgstr "sidebar article count" - -#: .\blog\models.py:320 -msgid "sidebar comment count" -msgstr "sidebar comment count" - -#: .\blog\models.py:321 -msgid "article comment count" -msgstr "article comment count" - -#: .\blog\models.py:322 -msgid "show adsense" -msgstr "show adsense" - -#: .\blog\models.py:324 -msgid "adsense code" -msgstr "adsense code" - -#: .\blog\models.py:325 -msgid "open site comment" -msgstr "open site comment" - -#: .\blog\models.py:352 -msgid "Website configuration" -msgstr "Website configuration" - -#: .\blog\models.py:360 -msgid "There can only be one configuration" -msgstr "There can only be one configuration" - -#: .\blog\views.py:348 -msgid "" -"Sorry, the page you requested is not found, please click the home page to " -"see other?" -msgstr "" -"Sorry, the page you requested is not found, please click the home page to " -"see other?" - -#: .\blog\views.py:356 -msgid "Sorry, the server is busy, please click the home page to see other?" -msgstr "Sorry, the server is busy, please click the home page to see other?" - -#: .\blog\views.py:369 -msgid "Sorry, you do not have permission to access this page?" -msgstr "Sorry, you do not have permission to access this page?" - -#: .\comments\admin.py:15 -msgid "Disable comments" -msgstr "Disable comments" - -#: .\comments\admin.py:16 -msgid "Enable comments" -msgstr "Enable comments" - -#: .\comments\admin.py:46 -msgid "User" -msgstr "User" - -#: .\comments\models.py:25 -msgid "parent comment" -msgstr "parent comment" - -#: .\comments\models.py:29 -msgid "enable" -msgstr "enable" - -#: .\comments\models.py:34 .\templates\blog\tags\article_info.html:30 -msgid "comment" -msgstr "comment" - -#: .\comments\utils.py:13 -msgid "Thanks for your comment" -msgstr "Thanks for your comment" - -#: .\comments\utils.py:15 -#, python-format -msgid "" -"

Thank you very much for your comments on this site

\n" -" You can visit %(article_title)s\n" -" to review your comments,\n" -" Thank you again!\n" -"
\n" -" If the link above cannot be opened, please copy this " -"link to your browser.\n" -" %(article_url)s" -msgstr "" -"

Thank you very much for your comments on this site

\n" -" You can visit %(article_title)s\n" -" to review your comments,\n" -" Thank you again!\n" -"
\n" -" If the link above cannot be opened, please copy this " -"link to your browser.\n" -" %(article_url)s" - -#: .\comments\utils.py:26 -#, python-format -msgid "" -"Your comment on " -"%(article_title)s
has \n" -" received a reply.
%(comment_body)s\n" -"
\n" -" go check it out!\n" -"
\n" -" If the link above cannot be opened, please copy this " -"link to your browser.\n" -" %(article_url)s\n" -" " -msgstr "" -"Your comment on " -"%(article_title)s
has \n" -" received a reply.
%(comment_body)s\n" -"
\n" -" go check it out!\n" -"
\n" -" If the link above cannot be opened, please copy this " -"link to your browser.\n" -" %(article_url)s\n" -" " - -#: .\djangoblog\logentryadmin.py:63 -msgid "object" -msgstr "object" - -#: .\djangoblog\settings.py:140 -msgid "English" -msgstr "English" - -#: .\djangoblog\settings.py:141 -msgid "Simplified Chinese" -msgstr "Simplified Chinese" - -#: .\djangoblog\settings.py:142 -msgid "Traditional Chinese" -msgstr "Traditional Chinese" - -#: .\oauth\models.py:30 -msgid "oauth user" -msgstr "oauth user" - -#: .\oauth\models.py:37 -msgid "weibo" -msgstr "weibo" - -#: .\oauth\models.py:38 -msgid "google" -msgstr "google" - -#: .\oauth\models.py:48 -msgid "callback url" -msgstr "callback url" - -#: .\oauth\models.py:59 -msgid "already exists" -msgstr "already exists" - -#: .\oauth\views.py:154 -#, python-format -msgid "" -"\n" -"

Congratulations, you have successfully bound your email address. You " -"can use\n" -" %(oauthuser_type)s to directly log in to this website without a " -"password.

\n" -" You are welcome to continue to follow this site, the address is\n" -" %(site)s\n" -" Thank you again!\n" -"
\n" -" If the link above cannot be opened, please copy this link to your " -"browser.\n" -" %(site)s\n" -" " -msgstr "" -"\n" -"

Congratulations, you have successfully bound your email address. You " -"can use\n" -" %(oauthuser_type)s to directly log in to this website without a " -"password.

\n" -" You are welcome to continue to follow this site, the address is\n" -" %(site)s\n" -" Thank you again!\n" -"
\n" -" If the link above cannot be opened, please copy this link to your " -"browser.\n" -" %(site)s\n" -" " - -#: .\oauth\views.py:165 -msgid "Congratulations on your successful binding!" -msgstr "Congratulations on your successful binding!" - -#: .\oauth\views.py:217 -#, python-format -msgid "" -"\n" -"

Please click the link below to bind your email

\n" -"\n" -" %(url)s\n" -"\n" -" Thank you again!\n" -"
\n" -" If the link above cannot be opened, please copy this link " -"to your browser.\n" -"
\n" -" %(url)s\n" -" " -msgstr "" -"\n" -"

Please click the link below to bind your email

\n" -"\n" -" %(url)s\n" -"\n" -" Thank you again!\n" -"
\n" -" If the link above cannot be opened, please copy this link " -"to your browser.\n" -"
\n" -" %(url)s\n" -" " - -#: .\oauth\views.py:228 .\oauth\views.py:240 -msgid "Bind your email" -msgstr "Bind your email" - -#: .\oauth\views.py:242 -msgid "" -"Congratulations, the binding is just one step away. Please log in to your " -"email to check the email to complete the binding. Thank you." -msgstr "" -"Congratulations, the binding is just one step away. Please log in to your " -"email to check the email to complete the binding. Thank you." - -#: .\oauth\views.py:245 -msgid "Binding successful" -msgstr "Binding successful" - -#: .\oauth\views.py:247 -#, python-format -msgid "" -"Congratulations, you have successfully bound your email address. You can use " -"%(oauthuser_type)s to directly log in to this website without a password. " -"You are welcome to continue to follow this site." -msgstr "" -"Congratulations, you have successfully bound your email address. You can use " -"%(oauthuser_type)s to directly log in to this website without a password. " -"You are welcome to continue to follow this site." - -#: .\templates\account\forget_password.html:7 -msgid "forget the password" -msgstr "forget the password" - -#: .\templates\account\forget_password.html:18 -msgid "get verification code" -msgstr "get verification code" - -#: .\templates\account\forget_password.html:19 -msgid "submit" -msgstr "submit" - -#: .\templates\account\login.html:36 -msgid "Create Account" -msgstr "Create Account" - -#: .\templates\account\login.html:42 -#, fuzzy -#| msgid "forget the password" -msgid "Forget Password" -msgstr "forget the password" - -#: .\templates\account\result.html:18 .\templates\blog\tags\sidebar.html:126 -msgid "login" -msgstr "login" - -#: .\templates\account\result.html:22 -msgid "back to the homepage" -msgstr "back to the homepage" - -#: .\templates\blog\article_archives.html:7 -#: .\templates\blog\article_archives.html:24 -msgid "article archive" -msgstr "article archive" - -#: .\templates\blog\article_archives.html:32 -msgid "year" -msgstr "year" - -#: .\templates\blog\article_archives.html:36 -msgid "month" -msgstr "month" - -#: .\templates\blog\tags\article_info.html:12 -msgid "pin to top" -msgstr "pin to top" - -#: .\templates\blog\tags\article_info.html:28 -msgid "comments" -msgstr "comments" - -#: .\templates\blog\tags\article_info.html:58 -msgid "toc" -msgstr "toc" - -#: .\templates\blog\tags\article_meta_info.html:6 -msgid "posted in" -msgstr "posted in" - -#: .\templates\blog\tags\article_meta_info.html:14 -msgid "and tagged" -msgstr "and tagged" - -#: .\templates\blog\tags\article_meta_info.html:25 -msgid "by " -msgstr "by" - -#: .\templates\blog\tags\article_meta_info.html:29 -#, python-format -msgid "" -"\n" -" title=\"View all articles published by " -"%(article.author.username)s\"\n" -" " -msgstr "" -"\n" -" title=\"View all articles published by " -"%(article.author.username)s\"\n" -" " - -#: .\templates\blog\tags\article_meta_info.html:44 -msgid "on" -msgstr "on" - -#: .\templates\blog\tags\article_meta_info.html:54 -msgid "edit" -msgstr "edit" - -#: .\templates\blog\tags\article_pagination.html:4 -msgid "article navigation" -msgstr "article navigation" - -#: .\templates\blog\tags\article_pagination.html:9 -msgid "earlier articles" -msgstr "earlier articles" - -#: .\templates\blog\tags\article_pagination.html:12 -msgid "newer articles" -msgstr "newer articles" - -#: .\templates\blog\tags\article_tag_list.html:5 -msgid "tags" -msgstr "tags" - -#: .\templates\blog\tags\sidebar.html:7 -msgid "search" -msgstr "search" - -#: .\templates\blog\tags\sidebar.html:50 -msgid "recent comments" -msgstr "recent comments" - -#: .\templates\blog\tags\sidebar.html:57 -msgid "published on" -msgstr "published on" - -#: .\templates\blog\tags\sidebar.html:65 -msgid "recent articles" -msgstr "recent articles" - -#: .\templates\blog\tags\sidebar.html:77 -msgid "bookmark" -msgstr "bookmark" - -#: .\templates\blog\tags\sidebar.html:96 -msgid "Tag Cloud" -msgstr "Tag Cloud" - -#: .\templates\blog\tags\sidebar.html:107 -msgid "Welcome to star or fork the source code of this site" -msgstr "Welcome to star or fork the source code of this site" - -#: .\templates\blog\tags\sidebar.html:118 -msgid "Function" -msgstr "Function" - -#: .\templates\blog\tags\sidebar.html:120 -msgid "management site" -msgstr "management site" - -#: .\templates\blog\tags\sidebar.html:122 -msgid "logout" -msgstr "logout" - -#: .\templates\blog\tags\sidebar.html:129 -msgid "Track record" -msgstr "Track record" - -#: .\templates\blog\tags\sidebar.html:135 -msgid "Click me to return to the top" -msgstr "Click me to return to the top" - -#: .\templates\oauth\oauth_applications.html:5 -#| msgid "login" -msgid "quick login" -msgstr "quick login" - -#: .\templates\share_layout\nav.html:26 -msgid "Article archive" -msgstr "Article archive" diff --git a/src/DjangoBlog/locale/zh_Hans/LC_MESSAGES/django.mo b/src/DjangoBlog/locale/zh_Hans/LC_MESSAGES/django.mo deleted file mode 100644 index a2d36e9..0000000 Binary files a/src/DjangoBlog/locale/zh_Hans/LC_MESSAGES/django.mo and /dev/null differ diff --git a/src/DjangoBlog/locale/zh_Hans/LC_MESSAGES/django.po b/src/DjangoBlog/locale/zh_Hans/LC_MESSAGES/django.po deleted file mode 100644 index 200b7e6..0000000 --- a/src/DjangoBlog/locale/zh_Hans/LC_MESSAGES/django.po +++ /dev/null @@ -1,667 +0,0 @@ -# SOME DESCRIPTIVE TITLE. -# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER -# This file is distributed under the same license as the PACKAGE package. -# FIRST AUTHOR , YEAR. -# -msgid "" -msgstr "" -"Project-Id-Version: PACKAGE VERSION\n" -"Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2023-09-13 16:02+0800\n" -"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" -"Last-Translator: FULL NAME \n" -"Language-Team: LANGUAGE \n" -"Language: \n" -"MIME-Version: 1.0\n" -"Content-Type: text/plain; charset=UTF-8\n" -"Content-Transfer-Encoding: 8bit\n" -"Plural-Forms: nplurals=1; plural=0;\n" - -#: .\accounts\admin.py:12 -msgid "password" -msgstr "密码" - -#: .\accounts\admin.py:13 -msgid "Enter password again" -msgstr "再次输入密码" - -#: .\accounts\admin.py:24 .\accounts\forms.py:89 -msgid "passwords do not match" -msgstr "密码不匹配" - -#: .\accounts\forms.py:36 -msgid "email already exists" -msgstr "邮箱已存在" - -#: .\accounts\forms.py:46 .\accounts\forms.py:50 -msgid "New password" -msgstr "新密码" - -#: .\accounts\forms.py:60 -msgid "Confirm password" -msgstr "确认密码" - -#: .\accounts\forms.py:70 .\accounts\forms.py:116 -msgid "Email" -msgstr "邮箱" - -#: .\accounts\forms.py:76 .\accounts\forms.py:80 -msgid "Code" -msgstr "验证码" - -#: .\accounts\forms.py:100 .\accounts\tests.py:194 -msgid "email does not exist" -msgstr "邮箱不存在" - -#: .\accounts\models.py:12 .\oauth\models.py:17 -msgid "nick name" -msgstr "昵称" - -#: .\accounts\models.py:13 .\blog\models.py:29 .\blog\models.py:266 -#: .\blog\models.py:284 .\comments\models.py:13 .\oauth\models.py:23 -#: .\oauth\models.py:53 -msgid "creation time" -msgstr "创建时间" - -#: .\accounts\models.py:14 .\comments\models.py:14 .\oauth\models.py:24 -#: .\oauth\models.py:54 -msgid "last modify time" -msgstr "最后修改时间" - -#: .\accounts\models.py:15 -msgid "create source" -msgstr "来源" - -#: .\accounts\models.py:33 .\djangoblog\logentryadmin.py:81 -msgid "user" -msgstr "用户" - -#: .\accounts\tests.py:216 .\accounts\utils.py:39 -msgid "Verification code error" -msgstr "验证码错误" - -#: .\accounts\utils.py:13 -msgid "Verify Email" -msgstr "验证邮箱" - -#: .\accounts\utils.py:21 -#, python-format -msgid "" -"You are resetting the password, the verification code is:%(code)s, valid " -"within 5 minutes, please keep it properly" -msgstr "您正在重置密码,验证码为:%(code)s,5分钟内有效 请妥善保管." - -#: .\blog\admin.py:13 .\blog\models.py:92 .\comments\models.py:17 -#: .\oauth\models.py:12 -msgid "author" -msgstr "作者" - -#: .\blog\admin.py:53 -msgid "Publish selected articles" -msgstr "发布选中的文章" - -#: .\blog\admin.py:54 -msgid "Draft selected articles" -msgstr "选中文章设为草稿" - -#: .\blog\admin.py:55 -msgid "Close article comments" -msgstr "关闭文章评论" - -#: .\blog\admin.py:56 -msgid "Open article comments" -msgstr "打开文章评论" - -#: .\blog\admin.py:89 .\blog\models.py:101 .\blog\models.py:183 -#: .\templates\blog\tags\sidebar.html:40 -msgid "category" -msgstr "分类目录" - -#: .\blog\models.py:20 .\blog\models.py:179 .\templates\share_layout\nav.html:8 -msgid "index" -msgstr "首页" - -#: .\blog\models.py:21 -msgid "list" -msgstr "列表" - -#: .\blog\models.py:22 -msgid "post" -msgstr "文章" - -#: .\blog\models.py:23 -msgid "all" -msgstr "所有" - -#: .\blog\models.py:24 -msgid "slide" -msgstr "侧边栏" - -#: .\blog\models.py:30 .\blog\models.py:267 .\blog\models.py:285 -msgid "modify time" -msgstr "修改时间" - -#: .\blog\models.py:63 -msgid "Draft" -msgstr "草稿" - -#: .\blog\models.py:64 -msgid "Published" -msgstr "发布" - -#: .\blog\models.py:67 -msgid "Open" -msgstr "打开" - -#: .\blog\models.py:68 -msgid "Close" -msgstr "关闭" - -#: .\blog\models.py:71 .\comments\admin.py:47 -msgid "Article" -msgstr "文章" - -#: .\blog\models.py:72 -msgid "Page" -msgstr "页面" - -#: .\blog\models.py:74 .\blog\models.py:280 -msgid "title" -msgstr "标题" - -#: .\blog\models.py:75 -msgid "body" -msgstr "内容" - -#: .\blog\models.py:77 -msgid "publish time" -msgstr "发布时间" - -#: .\blog\models.py:79 -msgid "status" -msgstr "状态" - -#: .\blog\models.py:84 -msgid "comment status" -msgstr "评论状态" - -#: .\blog\models.py:88 .\oauth\models.py:43 -msgid "type" -msgstr "类型" - -#: .\blog\models.py:89 -msgid "views" -msgstr "阅读量" - -#: .\blog\models.py:97 .\blog\models.py:258 .\blog\models.py:282 -msgid "order" -msgstr "排序" - -#: .\blog\models.py:98 -msgid "show toc" -msgstr "显示目录" - -#: .\blog\models.py:105 .\blog\models.py:249 -msgid "tag" -msgstr "标签" - -#: .\blog\models.py:115 .\comments\models.py:21 -msgid "article" -msgstr "文章" - -#: .\blog\models.py:171 -msgid "category name" -msgstr "分类名" - -#: .\blog\models.py:174 -msgid "parent category" -msgstr "上级分类" - -#: .\blog\models.py:234 -msgid "tag name" -msgstr "标签名" - -#: .\blog\models.py:256 -msgid "link name" -msgstr "链接名" - -#: .\blog\models.py:257 .\blog\models.py:271 -msgid "link" -msgstr "链接" - -#: .\blog\models.py:260 -msgid "is show" -msgstr "是否显示" - -#: .\blog\models.py:262 -msgid "show type" -msgstr "显示类型" - -#: .\blog\models.py:281 -msgid "content" -msgstr "内容" - -#: .\blog\models.py:283 .\oauth\models.py:52 -msgid "is enable" -msgstr "是否启用" - -#: .\blog\models.py:289 -msgid "sidebar" -msgstr "侧边栏" - -#: .\blog\models.py:299 -msgid "site name" -msgstr "站点名称" - -#: .\blog\models.py:305 -msgid "site description" -msgstr "站点描述" - -#: .\blog\models.py:311 -msgid "site seo description" -msgstr "站点SEO描述" - -#: .\blog\models.py:313 -msgid "site keywords" -msgstr "关键字" - -#: .\blog\models.py:318 -msgid "article sub length" -msgstr "文章摘要长度" - -#: .\blog\models.py:319 -msgid "sidebar article count" -msgstr "侧边栏文章数目" - -#: .\blog\models.py:320 -msgid "sidebar comment count" -msgstr "侧边栏评论数目" - -#: .\blog\models.py:321 -msgid "article comment count" -msgstr "文章页面默认显示评论数目" - -#: .\blog\models.py:322 -msgid "show adsense" -msgstr "是否显示广告" - -#: .\blog\models.py:324 -msgid "adsense code" -msgstr "广告内容" - -#: .\blog\models.py:325 -msgid "open site comment" -msgstr "公共头部" - -#: .\blog\models.py:352 -msgid "Website configuration" -msgstr "网站配置" - -#: .\blog\models.py:360 -msgid "There can only be one configuration" -msgstr "只能有一个配置" - -#: .\blog\views.py:348 -msgid "" -"Sorry, the page you requested is not found, please click the home page to " -"see other?" -msgstr "抱歉,你所访问的页面找不到,请点击首页看看别的?" - -#: .\blog\views.py:356 -msgid "Sorry, the server is busy, please click the home page to see other?" -msgstr "抱歉,服务出错了,请点击首页看看别的?" - -#: .\blog\views.py:369 -msgid "Sorry, you do not have permission to access this page?" -msgstr "抱歉,你没用权限访问此页面。" - -#: .\comments\admin.py:15 -msgid "Disable comments" -msgstr "禁用评论" - -#: .\comments\admin.py:16 -msgid "Enable comments" -msgstr "启用评论" - -#: .\comments\admin.py:46 -msgid "User" -msgstr "用户" - -#: .\comments\models.py:25 -msgid "parent comment" -msgstr "上级评论" - -#: .\comments\models.py:29 -msgid "enable" -msgstr "启用" - -#: .\comments\models.py:34 .\templates\blog\tags\article_info.html:30 -msgid "comment" -msgstr "评论" - -#: .\comments\utils.py:13 -msgid "Thanks for your comment" -msgstr "感谢你的评论" - -#: .\comments\utils.py:15 -#, python-format -msgid "" -"

Thank you very much for your comments on this site

\n" -" You can visit %(article_title)s\n" -" to review your comments,\n" -" Thank you again!\n" -"
\n" -" If the link above cannot be opened, please copy this " -"link to your browser.\n" -" %(article_url)s" -msgstr "" -"

非常感谢您对此网站的评论

\n" -" 您可以访问%(article_title)s\n" -"查看您的评论,\n" -"再次感谢您!\n" -"
\n" -" 如果上面的链接打不开,请复制此链接链接到您的浏览器。\n" -"%(article_url)s" - -#: .\comments\utils.py:26 -#, python-format -msgid "" -"Your comment on " -"%(article_title)s
has \n" -" received a reply.
%(comment_body)s\n" -"
\n" -" go check it out!\n" -"
\n" -" If the link above cannot be opened, please copy this " -"link to your browser.\n" -" %(article_url)s\n" -" " -msgstr "" -"您对 %(article_title)s
" -"的评论有\n" -" 收到回复。
%(comment_body)s\n" -"
\n" -"快去看看吧!\n" -"
\n" -" 如果上面的链接打不开,请复制此链接链接到您的浏览器。\n" -" %(article_url)s\n" -" " - -#: .\djangoblog\logentryadmin.py:63 -msgid "object" -msgstr "对象" - -#: .\djangoblog\settings.py:140 -msgid "English" -msgstr "英文" - -#: .\djangoblog\settings.py:141 -msgid "Simplified Chinese" -msgstr "简体中文" - -#: .\djangoblog\settings.py:142 -msgid "Traditional Chinese" -msgstr "繁体中文" - -#: .\oauth\models.py:30 -msgid "oauth user" -msgstr "第三方用户" - -#: .\oauth\models.py:37 -msgid "weibo" -msgstr "微博" - -#: .\oauth\models.py:38 -msgid "google" -msgstr "谷歌" - -#: .\oauth\models.py:48 -msgid "callback url" -msgstr "回调地址" - -#: .\oauth\models.py:59 -msgid "already exists" -msgstr "已经存在" - -#: .\oauth\views.py:154 -#, python-format -msgid "" -"\n" -"

Congratulations, you have successfully bound your email address. You " -"can use\n" -" %(oauthuser_type)s to directly log in to this website without a " -"password.

\n" -" You are welcome to continue to follow this site, the address is\n" -" %(site)s\n" -" Thank you again!\n" -"
\n" -" If the link above cannot be opened, please copy this link to your " -"browser.\n" -" %(site)s\n" -" " -msgstr "" -"\n" -"

恭喜你已经绑定成功 你可以使用\n" -" %(oauthuser_type)s 来免密登录本站

\n" -" 欢迎继续关注本站, 地址是\n" -" %(site)s\n" -" 再次感谢你\n" -"
\n" -" 如果上面链接无法打开,请复制此链接到你的浏览器 \n" -" %(site)s\n" -" " - -#: .\oauth\views.py:165 -msgid "Congratulations on your successful binding!" -msgstr "恭喜你绑定成功" - -#: .\oauth\views.py:217 -#, python-format -msgid "" -"\n" -"

Please click the link below to bind your email

\n" -"\n" -" %(url)s\n" -"\n" -" Thank you again!\n" -"
\n" -" If the link above cannot be opened, please copy this link " -"to your browser.\n" -"
\n" -" %(url)s\n" -" " -msgstr "" -"\n" -"

请点击下面的链接绑定您的邮箱

\n" -"\n" -" %(url)s\n" -"\n" -"再次感谢您!\n" -"
\n" -"如果上面的链接打不开,请复制此链接到您的浏览器。\n" -"%(url)s\n" -" " - -#: .\oauth\views.py:228 .\oauth\views.py:240 -msgid "Bind your email" -msgstr "绑定邮箱" - -#: .\oauth\views.py:242 -msgid "" -"Congratulations, the binding is just one step away. Please log in to your " -"email to check the email to complete the binding. Thank you." -msgstr "恭喜您,还差一步就绑定成功了,请登录您的邮箱查看邮件完成绑定,谢谢。" - -#: .\oauth\views.py:245 -msgid "Binding successful" -msgstr "绑定成功" - -#: .\oauth\views.py:247 -#, python-format -msgid "" -"Congratulations, you have successfully bound your email address. You can use " -"%(oauthuser_type)s to directly log in to this website without a password. " -"You are welcome to continue to follow this site." -msgstr "" -"恭喜您绑定成功,您以后可以使用%(oauthuser_type)s来直接免密码登录本站啦,感谢" -"您对本站对关注。" - -#: .\templates\account\forget_password.html:7 -msgid "forget the password" -msgstr "忘记密码" - -#: .\templates\account\forget_password.html:18 -msgid "get verification code" -msgstr "获取验证码" - -#: .\templates\account\forget_password.html:19 -msgid "submit" -msgstr "提交" - -#: .\templates\account\login.html:36 -msgid "Create Account" -msgstr "创建账号" - -#: .\templates\account\login.html:42 -#| msgid "forget the password" -msgid "Forget Password" -msgstr "忘记密码" - -#: .\templates\account\result.html:18 .\templates\blog\tags\sidebar.html:126 -msgid "login" -msgstr "登录" - -#: .\templates\account\result.html:22 -msgid "back to the homepage" -msgstr "返回首页吧" - -#: .\templates\blog\article_archives.html:7 -#: .\templates\blog\article_archives.html:24 -msgid "article archive" -msgstr "文章归档" - -#: .\templates\blog\article_archives.html:32 -msgid "year" -msgstr "年" - -#: .\templates\blog\article_archives.html:36 -msgid "month" -msgstr "月" - -#: .\templates\blog\tags\article_info.html:12 -msgid "pin to top" -msgstr "置顶" - -#: .\templates\blog\tags\article_info.html:28 -msgid "comments" -msgstr "评论" - -#: .\templates\blog\tags\article_info.html:58 -msgid "toc" -msgstr "目录" - -#: .\templates\blog\tags\article_meta_info.html:6 -msgid "posted in" -msgstr "发布于" - -#: .\templates\blog\tags\article_meta_info.html:14 -msgid "and tagged" -msgstr "并标记为" - -#: .\templates\blog\tags\article_meta_info.html:25 -msgid "by " -msgstr "由" - -#: .\templates\blog\tags\article_meta_info.html:29 -#, python-format -msgid "" -"\n" -" title=\"View all articles published by " -"%(article.author.username)s\"\n" -" " -msgstr "" -"\n" -" title=\"查看所有由 %(article.author.username)s\"发布的文章\n" -" " - -#: .\templates\blog\tags\article_meta_info.html:44 -msgid "on" -msgstr "在" - -#: .\templates\blog\tags\article_meta_info.html:54 -msgid "edit" -msgstr "编辑" - -#: .\templates\blog\tags\article_pagination.html:4 -msgid "article navigation" -msgstr "文章导航" - -#: .\templates\blog\tags\article_pagination.html:9 -msgid "earlier articles" -msgstr "早期文章" - -#: .\templates\blog\tags\article_pagination.html:12 -msgid "newer articles" -msgstr "较新文章" - -#: .\templates\blog\tags\article_tag_list.html:5 -msgid "tags" -msgstr "标签" - -#: .\templates\blog\tags\sidebar.html:7 -msgid "search" -msgstr "搜索" - -#: .\templates\blog\tags\sidebar.html:50 -msgid "recent comments" -msgstr "近期评论" - -#: .\templates\blog\tags\sidebar.html:57 -msgid "published on" -msgstr "发表于" - -#: .\templates\blog\tags\sidebar.html:65 -msgid "recent articles" -msgstr "近期文章" - -#: .\templates\blog\tags\sidebar.html:77 -msgid "bookmark" -msgstr "书签" - -#: .\templates\blog\tags\sidebar.html:96 -msgid "Tag Cloud" -msgstr "标签云" - -#: .\templates\blog\tags\sidebar.html:107 -msgid "Welcome to star or fork the source code of this site" -msgstr "欢迎您STAR或者FORK本站源代码" - -#: .\templates\blog\tags\sidebar.html:118 -msgid "Function" -msgstr "功能" - -#: .\templates\blog\tags\sidebar.html:120 -msgid "management site" -msgstr "管理站点" - -#: .\templates\blog\tags\sidebar.html:122 -msgid "logout" -msgstr "登出" - -#: .\templates\blog\tags\sidebar.html:129 -msgid "Track record" -msgstr "运动轨迹记录" - -#: .\templates\blog\tags\sidebar.html:135 -msgid "Click me to return to the top" -msgstr "点我返回顶部" - -#: .\templates\oauth\oauth_applications.html:5 -#| msgid "login" -msgid "quick login" -msgstr "快捷登录" - -#: .\templates\share_layout\nav.html:26 -msgid "Article archive" -msgstr "文章归档" diff --git a/src/DjangoBlog/locale/zh_Hant/LC_MESSAGES/django.mo b/src/DjangoBlog/locale/zh_Hant/LC_MESSAGES/django.mo deleted file mode 100644 index fe2ea17..0000000 Binary files a/src/DjangoBlog/locale/zh_Hant/LC_MESSAGES/django.mo and /dev/null differ diff --git a/src/DjangoBlog/locale/zh_Hant/LC_MESSAGES/django.po b/src/DjangoBlog/locale/zh_Hant/LC_MESSAGES/django.po deleted file mode 100644 index a2920ce..0000000 --- a/src/DjangoBlog/locale/zh_Hant/LC_MESSAGES/django.po +++ /dev/null @@ -1,668 +0,0 @@ -# SOME DESCRIPTIVE TITLE. -# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER -# This file is distributed under the same license as the PACKAGE package. -# FIRST AUTHOR , YEAR. -# -msgid "" -msgstr "" -"Project-Id-Version: PACKAGE VERSION\n" -"Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2023-09-13 16:02+0800\n" -"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" -"Last-Translator: FULL NAME \n" -"Language-Team: LANGUAGE \n" -"Language: \n" -"MIME-Version: 1.0\n" -"Content-Type: text/plain; charset=UTF-8\n" -"Content-Transfer-Encoding: 8bit\n" -"Plural-Forms: nplurals=1; plural=0;\n" - -#: .\accounts\admin.py:12 -msgid "password" -msgstr "密碼" - -#: .\accounts\admin.py:13 -msgid "Enter password again" -msgstr "再次輸入密碼" - -#: .\accounts\admin.py:24 .\accounts\forms.py:89 -msgid "passwords do not match" -msgstr "密碼不匹配" - -#: .\accounts\forms.py:36 -msgid "email already exists" -msgstr "郵箱已存在" - -#: .\accounts\forms.py:46 .\accounts\forms.py:50 -msgid "New password" -msgstr "新密碼" - -#: .\accounts\forms.py:60 -msgid "Confirm password" -msgstr "確認密碼" - -#: .\accounts\forms.py:70 .\accounts\forms.py:116 -msgid "Email" -msgstr "郵箱" - -#: .\accounts\forms.py:76 .\accounts\forms.py:80 -msgid "Code" -msgstr "驗證碼" - -#: .\accounts\forms.py:100 .\accounts\tests.py:194 -msgid "email does not exist" -msgstr "郵箱不存在" - -#: .\accounts\models.py:12 .\oauth\models.py:17 -msgid "nick name" -msgstr "昵稱" - -#: .\accounts\models.py:13 .\blog\models.py:29 .\blog\models.py:266 -#: .\blog\models.py:284 .\comments\models.py:13 .\oauth\models.py:23 -#: .\oauth\models.py:53 -msgid "creation time" -msgstr "創建時間" - -#: .\accounts\models.py:14 .\comments\models.py:14 .\oauth\models.py:24 -#: .\oauth\models.py:54 -msgid "last modify time" -msgstr "最後修改時間" - -#: .\accounts\models.py:15 -msgid "create source" -msgstr "來源" - -#: .\accounts\models.py:33 .\djangoblog\logentryadmin.py:81 -msgid "user" -msgstr "用戶" - -#: .\accounts\tests.py:216 .\accounts\utils.py:39 -msgid "Verification code error" -msgstr "驗證碼錯誤" - -#: .\accounts\utils.py:13 -msgid "Verify Email" -msgstr "驗證郵箱" - -#: .\accounts\utils.py:21 -#, python-format -msgid "" -"You are resetting the password, the verification code is:%(code)s, valid " -"within 5 minutes, please keep it properly" -msgstr "您正在重置密碼,驗證碼為:%(code)s,5分鐘內有效 請妥善保管." - -#: .\blog\admin.py:13 .\blog\models.py:92 .\comments\models.py:17 -#: .\oauth\models.py:12 -msgid "author" -msgstr "作者" - -#: .\blog\admin.py:53 -msgid "Publish selected articles" -msgstr "發布選中的文章" - -#: .\blog\admin.py:54 -msgid "Draft selected articles" -msgstr "選中文章設為草稿" - -#: .\blog\admin.py:55 -msgid "Close article comments" -msgstr "關閉文章評論" - -#: .\blog\admin.py:56 -msgid "Open article comments" -msgstr "打開文章評論" - -#: .\blog\admin.py:89 .\blog\models.py:101 .\blog\models.py:183 -#: .\templates\blog\tags\sidebar.html:40 -msgid "category" -msgstr "分類目錄" - -#: .\blog\models.py:20 .\blog\models.py:179 .\templates\share_layout\nav.html:8 -msgid "index" -msgstr "首頁" - -#: .\blog\models.py:21 -msgid "list" -msgstr "列表" - -#: .\blog\models.py:22 -msgid "post" -msgstr "文章" - -#: .\blog\models.py:23 -msgid "all" -msgstr "所有" - -#: .\blog\models.py:24 -msgid "slide" -msgstr "側邊欄" - -#: .\blog\models.py:30 .\blog\models.py:267 .\blog\models.py:285 -msgid "modify time" -msgstr "修改時間" - -#: .\blog\models.py:63 -msgid "Draft" -msgstr "草稿" - -#: .\blog\models.py:64 -msgid "Published" -msgstr "發布" - -#: .\blog\models.py:67 -msgid "Open" -msgstr "打開" - -#: .\blog\models.py:68 -msgid "Close" -msgstr "關閉" - -#: .\blog\models.py:71 .\comments\admin.py:47 -msgid "Article" -msgstr "文章" - -#: .\blog\models.py:72 -msgid "Page" -msgstr "頁面" - -#: .\blog\models.py:74 .\blog\models.py:280 -msgid "title" -msgstr "標題" - -#: .\blog\models.py:75 -msgid "body" -msgstr "內容" - -#: .\blog\models.py:77 -msgid "publish time" -msgstr "發布時間" - -#: .\blog\models.py:79 -msgid "status" -msgstr "狀態" - -#: .\blog\models.py:84 -msgid "comment status" -msgstr "評論狀態" - -#: .\blog\models.py:88 .\oauth\models.py:43 -msgid "type" -msgstr "類型" - -#: .\blog\models.py:89 -msgid "views" -msgstr "閱讀量" - -#: .\blog\models.py:97 .\blog\models.py:258 .\blog\models.py:282 -msgid "order" -msgstr "排序" - -#: .\blog\models.py:98 -msgid "show toc" -msgstr "顯示目錄" - -#: .\blog\models.py:105 .\blog\models.py:249 -msgid "tag" -msgstr "標簽" - -#: .\blog\models.py:115 .\comments\models.py:21 -msgid "article" -msgstr "文章" - -#: .\blog\models.py:171 -msgid "category name" -msgstr "分類名" - -#: .\blog\models.py:174 -msgid "parent category" -msgstr "上級分類" - -#: .\blog\models.py:234 -msgid "tag name" -msgstr "標簽名" - -#: .\blog\models.py:256 -msgid "link name" -msgstr "鏈接名" - -#: .\blog\models.py:257 .\blog\models.py:271 -msgid "link" -msgstr "鏈接" - -#: .\blog\models.py:260 -msgid "is show" -msgstr "是否顯示" - -#: .\blog\models.py:262 -msgid "show type" -msgstr "顯示類型" - -#: .\blog\models.py:281 -msgid "content" -msgstr "內容" - -#: .\blog\models.py:283 .\oauth\models.py:52 -msgid "is enable" -msgstr "是否啟用" - -#: .\blog\models.py:289 -msgid "sidebar" -msgstr "側邊欄" - -#: .\blog\models.py:299 -msgid "site name" -msgstr "站點名稱" - -#: .\blog\models.py:305 -msgid "site description" -msgstr "站點描述" - -#: .\blog\models.py:311 -msgid "site seo description" -msgstr "站點SEO描述" - -#: .\blog\models.py:313 -msgid "site keywords" -msgstr "關鍵字" - -#: .\blog\models.py:318 -msgid "article sub length" -msgstr "文章摘要長度" - -#: .\blog\models.py:319 -msgid "sidebar article count" -msgstr "側邊欄文章數目" - -#: .\blog\models.py:320 -msgid "sidebar comment count" -msgstr "側邊欄評論數目" - -#: .\blog\models.py:321 -msgid "article comment count" -msgstr "文章頁面默認顯示評論數目" - -#: .\blog\models.py:322 -msgid "show adsense" -msgstr "是否顯示廣告" - -#: .\blog\models.py:324 -msgid "adsense code" -msgstr "廣告內容" - -#: .\blog\models.py:325 -msgid "open site comment" -msgstr "公共頭部" - -#: .\blog\models.py:352 -msgid "Website configuration" -msgstr "網站配置" - -#: .\blog\models.py:360 -msgid "There can only be one configuration" -msgstr "只能有一個配置" - -#: .\blog\views.py:348 -msgid "" -"Sorry, the page you requested is not found, please click the home page to " -"see other?" -msgstr "抱歉,你所訪問的頁面找不到,請點擊首頁看看別的?" - -#: .\blog\views.py:356 -msgid "Sorry, the server is busy, please click the home page to see other?" -msgstr "抱歉,服務出錯了,請點擊首頁看看別的?" - -#: .\blog\views.py:369 -msgid "Sorry, you do not have permission to access this page?" -msgstr "抱歉,你沒用權限訪問此頁面。" - -#: .\comments\admin.py:15 -msgid "Disable comments" -msgstr "禁用評論" - -#: .\comments\admin.py:16 -msgid "Enable comments" -msgstr "啟用評論" - -#: .\comments\admin.py:46 -msgid "User" -msgstr "用戶" - -#: .\comments\models.py:25 -msgid "parent comment" -msgstr "上級評論" - -#: .\comments\models.py:29 -msgid "enable" -msgstr "啟用" - -#: .\comments\models.py:34 .\templates\blog\tags\article_info.html:30 -msgid "comment" -msgstr "評論" - -#: .\comments\utils.py:13 -msgid "Thanks for your comment" -msgstr "感謝你的評論" - -#: .\comments\utils.py:15 -#, python-format -msgid "" -"

Thank you very much for your comments on this site

\n" -" You can visit %(article_title)s\n" -" to review your comments,\n" -" Thank you again!\n" -"
\n" -" If the link above cannot be opened, please copy this " -"link to your browser.\n" -" %(article_url)s" -msgstr "" -"

非常感謝您對此網站的評論

\n" -" 您可以訪問%(article_title)s\n" -"查看您的評論,\n" -"再次感謝您!\n" -"
\n" -" 如果上面的鏈接打不開,請復製此鏈接鏈接到您的瀏覽器。\n" -"%(article_url)s" - -#: .\comments\utils.py:26 -#, python-format -msgid "" -"Your comment on " -"%(article_title)s
has \n" -" received a reply.
%(comment_body)s\n" -"
\n" -" go check it out!\n" -"
\n" -" If the link above cannot be opened, please copy this " -"link to your browser.\n" -" %(article_url)s\n" -" " -msgstr "" -"您對 %(article_title)s
" -"的評論有\n" -" 收到回復。
%(comment_body)s\n" -"
\n" -"快去看看吧!\n" -"
\n" -" 如果上面的鏈接打不開,請復製此鏈接鏈接到您的瀏覽器。\n" -" %(article_url)s\n" -" " - -#: .\djangoblog\logentryadmin.py:63 -msgid "object" -msgstr "對象" - -#: .\djangoblog\settings.py:140 -msgid "English" -msgstr "英文" - -#: .\djangoblog\settings.py:141 -msgid "Simplified Chinese" -msgstr "簡體中文" - -#: .\djangoblog\settings.py:142 -msgid "Traditional Chinese" -msgstr "繁體中文" - -#: .\oauth\models.py:30 -msgid "oauth user" -msgstr "第三方用戶" - -#: .\oauth\models.py:37 -msgid "weibo" -msgstr "微博" - -#: .\oauth\models.py:38 -msgid "google" -msgstr "谷歌" - -#: .\oauth\models.py:48 -msgid "callback url" -msgstr "回調地址" - -#: .\oauth\models.py:59 -msgid "already exists" -msgstr "已經存在" - -#: .\oauth\views.py:154 -#, python-format -msgid "" -"\n" -"

Congratulations, you have successfully bound your email address. You " -"can use\n" -" %(oauthuser_type)s to directly log in to this website without a " -"password.

\n" -" You are welcome to continue to follow this site, the address is\n" -" %(site)s\n" -" Thank you again!\n" -"
\n" -" If the link above cannot be opened, please copy this link to your " -"browser.\n" -" %(site)s\n" -" " -msgstr "" -"\n" -"

恭喜你已經綁定成功 你可以使用\n" -" %(oauthuser_type)s 來免密登錄本站

\n" -" 歡迎繼續關註本站, 地址是\n" -" %(site)s\n" -" 再次感謝你\n" -"
\n" -" 如果上面鏈接無法打開,請復製此鏈接到你的瀏覽器 \n" -" %(site)s\n" -" " - -#: .\oauth\views.py:165 -msgid "Congratulations on your successful binding!" -msgstr "恭喜你綁定成功" - -#: .\oauth\views.py:217 -#, python-format -msgid "" -"\n" -"

Please click the link below to bind your email

\n" -"\n" -" %(url)s\n" -"\n" -" Thank you again!\n" -"
\n" -" If the link above cannot be opened, please copy this link " -"to your browser.\n" -"
\n" -" %(url)s\n" -" " -msgstr "" -"\n" -"

請點擊下面的鏈接綁定您的郵箱

\n" -"\n" -" %(url)s\n" -"\n" -"再次感謝您!\n" -"
\n" -"如果上面的鏈接打不開,請復製此鏈接到您的瀏覽器。\n" -"%(url)s\n" -" " - -#: .\oauth\views.py:228 .\oauth\views.py:240 -msgid "Bind your email" -msgstr "綁定郵箱" - -#: .\oauth\views.py:242 -msgid "" -"Congratulations, the binding is just one step away. Please log in to your " -"email to check the email to complete the binding. Thank you." -msgstr "恭喜您,還差一步就綁定成功了,請登錄您的郵箱查看郵件完成綁定,謝謝。" - -#: .\oauth\views.py:245 -msgid "Binding successful" -msgstr "綁定成功" - -#: .\oauth\views.py:247 -#, python-format -msgid "" -"Congratulations, you have successfully bound your email address. You can use " -"%(oauthuser_type)s to directly log in to this website without a password. " -"You are welcome to continue to follow this site." -msgstr "" -"恭喜您綁定成功,您以後可以使用%(oauthuser_type)s來直接免密碼登錄本站啦,感謝" -"您對本站對關註。" - -#: .\templates\account\forget_password.html:7 -msgid "forget the password" -msgstr "忘記密碼" - -#: .\templates\account\forget_password.html:18 -msgid "get verification code" -msgstr "獲取驗證碼" - -#: .\templates\account\forget_password.html:19 -msgid "submit" -msgstr "提交" - -#: .\templates\account\login.html:36 -msgid "Create Account" -msgstr "創建賬號" - -#: .\templates\account\login.html:42 -#, fuzzy -#| msgid "forget the password" -msgid "Forget Password" -msgstr "忘記密碼" - -#: .\templates\account\result.html:18 .\templates\blog\tags\sidebar.html:126 -msgid "login" -msgstr "登錄" - -#: .\templates\account\result.html:22 -msgid "back to the homepage" -msgstr "返回首頁吧" - -#: .\templates\blog\article_archives.html:7 -#: .\templates\blog\article_archives.html:24 -msgid "article archive" -msgstr "文章歸檔" - -#: .\templates\blog\article_archives.html:32 -msgid "year" -msgstr "年" - -#: .\templates\blog\article_archives.html:36 -msgid "month" -msgstr "月" - -#: .\templates\blog\tags\article_info.html:12 -msgid "pin to top" -msgstr "置頂" - -#: .\templates\blog\tags\article_info.html:28 -msgid "comments" -msgstr "評論" - -#: .\templates\blog\tags\article_info.html:58 -msgid "toc" -msgstr "目錄" - -#: .\templates\blog\tags\article_meta_info.html:6 -msgid "posted in" -msgstr "發布於" - -#: .\templates\blog\tags\article_meta_info.html:14 -msgid "and tagged" -msgstr "並標記為" - -#: .\templates\blog\tags\article_meta_info.html:25 -msgid "by " -msgstr "由" - -#: .\templates\blog\tags\article_meta_info.html:29 -#, python-format -msgid "" -"\n" -" title=\"View all articles published by " -"%(article.author.username)s\"\n" -" " -msgstr "" -"\n" -" title=\"查看所有由 %(article.author.username)s\"發布的文章\n" -" " - -#: .\templates\blog\tags\article_meta_info.html:44 -msgid "on" -msgstr "在" - -#: .\templates\blog\tags\article_meta_info.html:54 -msgid "edit" -msgstr "編輯" - -#: .\templates\blog\tags\article_pagination.html:4 -msgid "article navigation" -msgstr "文章導航" - -#: .\templates\blog\tags\article_pagination.html:9 -msgid "earlier articles" -msgstr "早期文章" - -#: .\templates\blog\tags\article_pagination.html:12 -msgid "newer articles" -msgstr "較新文章" - -#: .\templates\blog\tags\article_tag_list.html:5 -msgid "tags" -msgstr "標簽" - -#: .\templates\blog\tags\sidebar.html:7 -msgid "search" -msgstr "搜索" - -#: .\templates\blog\tags\sidebar.html:50 -msgid "recent comments" -msgstr "近期評論" - -#: .\templates\blog\tags\sidebar.html:57 -msgid "published on" -msgstr "發表於" - -#: .\templates\blog\tags\sidebar.html:65 -msgid "recent articles" -msgstr "近期文章" - -#: .\templates\blog\tags\sidebar.html:77 -msgid "bookmark" -msgstr "書簽" - -#: .\templates\blog\tags\sidebar.html:96 -msgid "Tag Cloud" -msgstr "標簽雲" - -#: .\templates\blog\tags\sidebar.html:107 -msgid "Welcome to star or fork the source code of this site" -msgstr "歡迎您STAR或者FORK本站源代碼" - -#: .\templates\blog\tags\sidebar.html:118 -msgid "Function" -msgstr "功能" - -#: .\templates\blog\tags\sidebar.html:120 -msgid "management site" -msgstr "管理站點" - -#: .\templates\blog\tags\sidebar.html:122 -msgid "logout" -msgstr "登出" - -#: .\templates\blog\tags\sidebar.html:129 -msgid "Track record" -msgstr "運動軌跡記錄" - -#: .\templates\blog\tags\sidebar.html:135 -msgid "Click me to return to the top" -msgstr "點我返回頂部" - -#: .\templates\oauth\oauth_applications.html:5 -#| msgid "login" -msgid "quick login" -msgstr "快捷登錄" - -#: .\templates\share_layout\nav.html:26 -msgid "Article archive" -msgstr "文章歸檔" diff --git a/src/DjangoBlog/manage.py b/src/DjangoBlog/manage.py deleted file mode 100755 index 919ba74..0000000 --- a/src/DjangoBlog/manage.py +++ /dev/null @@ -1,22 +0,0 @@ -#!/usr/bin/env python -import os -import sys - -if __name__ == "__main__": - os.environ.setdefault("DJANGO_SETTINGS_MODULE", "djangoblog.settings") - try: - from django.core.management import execute_from_command_line - except ImportError: - # The above import may fail for some other reason. Ensure that the - # issue is really that Django is missing to avoid masking other - # exceptions on Python 2. - try: - import django - except ImportError: - raise ImportError( - "Couldn't import Django. Are you sure it's installed and " - "available on your PYTHONPATH environment variable? Did you " - "forget to activate a virtual environment?" - ) - raise - execute_from_command_line(sys.argv) diff --git a/src/DjangoBlog/oauth/__init__.py b/src/DjangoBlog/oauth/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/src/DjangoBlog/oauth/admin.py b/src/DjangoBlog/oauth/admin.py deleted file mode 100644 index 57eab5f..0000000 --- a/src/DjangoBlog/oauth/admin.py +++ /dev/null @@ -1,54 +0,0 @@ -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/DjangoBlog/oauth/apps.py b/src/DjangoBlog/oauth/apps.py deleted file mode 100644 index 17fcea2..0000000 --- a/src/DjangoBlog/oauth/apps.py +++ /dev/null @@ -1,5 +0,0 @@ -from django.apps import AppConfig - - -class OauthConfig(AppConfig): - name = 'oauth' diff --git a/src/DjangoBlog/oauth/forms.py b/src/DjangoBlog/oauth/forms.py deleted file mode 100644 index 0e4ede3..0000000 --- a/src/DjangoBlog/oauth/forms.py +++ /dev/null @@ -1,12 +0,0 @@ -from django.contrib.auth.forms import forms -from django.forms import widgets - - -class RequireEmailForm(forms.Form): - email = forms.EmailField(label='电子邮箱', required=True) - oauthid = forms.IntegerField(widget=forms.HiddenInput, required=False) - - def __init__(self, *args, **kwargs): - super(RequireEmailForm, self).__init__(*args, **kwargs) - self.fields['email'].widget = widgets.EmailInput( - attrs={'placeholder': "email", "class": "form-control"}) diff --git a/src/DjangoBlog/oauth/migrations/0001_initial.py b/src/DjangoBlog/oauth/migrations/0001_initial.py deleted file mode 100644 index 3aa3e03..0000000 --- a/src/DjangoBlog/oauth/migrations/0001_initial.py +++ /dev/null @@ -1,57 +0,0 @@ -# Generated by Django 4.1.7 on 2023-03-07 09:53 - -from django.conf import settings -from django.db import migrations, models -import django.db.models.deletion -import django.utils.timezone - - -class Migration(migrations.Migration): - - initial = True - - dependencies = [ - migrations.swappable_dependency(settings.AUTH_USER_MODEL), - ] - - operations = [ - migrations.CreateModel( - name='OAuthConfig', - fields=[ - ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('type', models.CharField(choices=[('weibo', '微博'), ('google', '谷歌'), ('github', 'GitHub'), ('facebook', 'FaceBook'), ('qq', 'QQ')], default='a', max_length=10, verbose_name='类型')), - ('appkey', models.CharField(max_length=200, verbose_name='AppKey')), - ('appsecret', models.CharField(max_length=200, verbose_name='AppSecret')), - ('callback_url', models.CharField(default='http://www.baidu.com', max_length=200, verbose_name='回调地址')), - ('is_enable', models.BooleanField(default=True, verbose_name='是否显示')), - ('created_time', models.DateTimeField(default=django.utils.timezone.now, verbose_name='创建时间')), - ('last_mod_time', models.DateTimeField(default=django.utils.timezone.now, verbose_name='修改时间')), - ], - options={ - 'verbose_name': 'oauth配置', - 'verbose_name_plural': 'oauth配置', - 'ordering': ['-created_time'], - }, - ), - migrations.CreateModel( - name='OAuthUser', - fields=[ - ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('openid', models.CharField(max_length=50)), - ('nickname', models.CharField(max_length=50, verbose_name='昵称')), - ('token', models.CharField(blank=True, max_length=150, null=True)), - ('picture', models.CharField(blank=True, max_length=350, null=True)), - ('type', models.CharField(max_length=50)), - ('email', models.CharField(blank=True, max_length=50, null=True)), - ('metadata', models.TextField(blank=True, null=True)), - ('created_time', models.DateTimeField(default=django.utils.timezone.now, verbose_name='创建时间')), - ('last_mod_time', models.DateTimeField(default=django.utils.timezone.now, verbose_name='修改时间')), - ('author', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL, verbose_name='用户')), - ], - options={ - 'verbose_name': 'oauth用户', - 'verbose_name_plural': 'oauth用户', - 'ordering': ['-created_time'], - }, - ), - ] diff --git a/src/DjangoBlog/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 deleted file mode 100644 index d5cc70e..0000000 --- a/src/DjangoBlog/oauth/migrations/0002_alter_oauthconfig_options_alter_oauthuser_options_and_more.py +++ /dev/null @@ -1,86 +0,0 @@ -# Generated by Django 4.2.5 on 2023-09-06 13:13 - -from django.conf import settings -from django.db import migrations, models -import django.db.models.deletion -import django.utils.timezone - - -class Migration(migrations.Migration): - - dependencies = [ - migrations.swappable_dependency(settings.AUTH_USER_MODEL), - ('oauth', '0001_initial'), - ] - - operations = [ - migrations.AlterModelOptions( - name='oauthconfig', - options={'ordering': ['-creation_time'], 'verbose_name': 'oauth配置', 'verbose_name_plural': 'oauth配置'}, - ), - migrations.AlterModelOptions( - name='oauthuser', - options={'ordering': ['-creation_time'], 'verbose_name': 'oauth user', 'verbose_name_plural': 'oauth user'}, - ), - migrations.RemoveField( - model_name='oauthconfig', - name='created_time', - ), - migrations.RemoveField( - model_name='oauthconfig', - name='last_mod_time', - ), - migrations.RemoveField( - model_name='oauthuser', - name='created_time', - ), - migrations.RemoveField( - model_name='oauthuser', - name='last_mod_time', - ), - migrations.AddField( - model_name='oauthconfig', - name='creation_time', - field=models.DateTimeField(default=django.utils.timezone.now, verbose_name='creation time'), - ), - migrations.AddField( - model_name='oauthconfig', - name='last_modify_time', - field=models.DateTimeField(default=django.utils.timezone.now, verbose_name='last modify time'), - ), - migrations.AddField( - model_name='oauthuser', - name='creation_time', - field=models.DateTimeField(default=django.utils.timezone.now, verbose_name='creation time'), - ), - migrations.AddField( - model_name='oauthuser', - name='last_modify_time', - field=models.DateTimeField(default=django.utils.timezone.now, verbose_name='last modify time'), - ), - migrations.AlterField( - model_name='oauthconfig', - name='callback_url', - field=models.CharField(default='', max_length=200, verbose_name='callback url'), - ), - migrations.AlterField( - model_name='oauthconfig', - name='is_enable', - field=models.BooleanField(default=True, verbose_name='is enable'), - ), - migrations.AlterField( - model_name='oauthconfig', - name='type', - field=models.CharField(choices=[('weibo', 'weibo'), ('google', 'google'), ('github', 'GitHub'), ('facebook', 'FaceBook'), ('qq', 'QQ')], default='a', max_length=10, verbose_name='type'), - ), - migrations.AlterField( - model_name='oauthuser', - name='author', - field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL, verbose_name='author'), - ), - migrations.AlterField( - model_name='oauthuser', - name='nickname', - field=models.CharField(max_length=50, verbose_name='nickname'), - ), - ] diff --git a/src/DjangoBlog/oauth/migrations/0003_alter_oauthuser_nickname.py b/src/DjangoBlog/oauth/migrations/0003_alter_oauthuser_nickname.py deleted file mode 100644 index 6af08eb..0000000 --- a/src/DjangoBlog/oauth/migrations/0003_alter_oauthuser_nickname.py +++ /dev/null @@ -1,18 +0,0 @@ -# Generated by Django 4.2.7 on 2024-01-26 02:41 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('oauth', '0002_alter_oauthconfig_options_alter_oauthuser_options_and_more'), - ] - - operations = [ - migrations.AlterField( - model_name='oauthuser', - name='nickname', - field=models.CharField(max_length=50, verbose_name='nick name'), - ), - ] diff --git a/src/DjangoBlog/oauth/migrations/__init__.py b/src/DjangoBlog/oauth/migrations/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/src/DjangoBlog/oauth/models.py b/src/DjangoBlog/oauth/models.py deleted file mode 100644 index be838ed..0000000 --- a/src/DjangoBlog/oauth/models.py +++ /dev/null @@ -1,67 +0,0 @@ -# Create your models here. -from django.conf import settings -from django.core.exceptions import ValidationError -from django.db import models -from django.utils.timezone import now -from django.utils.translation import gettext_lazy as _ - - -class OAuthUser(models.Model): - author = models.ForeignKey( - settings.AUTH_USER_MODEL, - verbose_name=_('author'), - blank=True, - null=True, - on_delete=models.CASCADE) - openid = models.CharField(max_length=50) - nickname = models.CharField(max_length=50, verbose_name=_('nick name')) - token = models.CharField(max_length=150, null=True, blank=True) - picture = models.CharField(max_length=350, blank=True, null=True) - type = models.CharField(blank=False, null=False, max_length=50) - email = models.CharField(max_length=50, null=True, blank=True) - metadata = models.TextField(null=True, blank=True) - creation_time = models.DateTimeField(_('creation time'), default=now) - last_modify_time = models.DateTimeField(_('last modify time'), default=now) - - def __str__(self): - return self.nickname - - class Meta: - verbose_name = _('oauth user') - verbose_name_plural = verbose_name - ordering = ['-creation_time'] - - -class OAuthConfig(models.Model): - TYPE = ( - ('weibo', _('weibo')), - ('google', _('google')), - ('github', 'GitHub'), - ('facebook', 'FaceBook'), - ('qq', 'QQ'), - ) - type = models.CharField(_('type'), max_length=10, choices=TYPE, default='a') - appkey = models.CharField(max_length=200, verbose_name='AppKey') - appsecret = models.CharField(max_length=200, verbose_name='AppSecret') - callback_url = models.CharField( - max_length=200, - verbose_name=_('callback url'), - blank=False, - default='') - is_enable = models.BooleanField( - _('is enable'), default=True, blank=False, null=False) - creation_time = models.DateTimeField(_('creation time'), default=now) - last_modify_time = models.DateTimeField(_('last modify time'), default=now) - - def clean(self): - if OAuthConfig.objects.filter( - type=self.type).exclude(id=self.id).count(): - raise ValidationError(_(self.type + _('already exists'))) - - def __str__(self): - return self.type - - class Meta: - verbose_name = 'oauth配置' - verbose_name_plural = verbose_name - ordering = ['-creation_time'] diff --git a/src/DjangoBlog/oauth/oauthmanager.py b/src/DjangoBlog/oauth/oauthmanager.py deleted file mode 100644 index 2e7ceef..0000000 --- a/src/DjangoBlog/oauth/oauthmanager.py +++ /dev/null @@ -1,504 +0,0 @@ -import json -import logging -import os -import urllib.parse -from abc import ABCMeta, abstractmethod - -import requests - -from djangoblog.utils import cache_decorator -from oauth.models import OAuthUser, OAuthConfig - -logger = logging.getLogger(__name__) - - -class OAuthAccessTokenException(Exception): - ''' - oauth授权失败异常 - ''' - - -class BaseOauthManager(metaclass=ABCMeta): - """获取用户授权""" - AUTH_URL = None - """获取token""" - TOKEN_URL = None - """获取用户信息""" - API_URL = None - '''icon图标名''' - ICON_NAME = None - - def __init__(self, access_token=None, openid=None): - self.access_token = access_token - self.openid = openid - - @property - def is_access_token_set(self): - return self.access_token is not None - - @property - def is_authorized(self): - return self.is_access_token_set and self.access_token is not None and self.openid is not None - - @abstractmethod - def get_authorization_url(self, nexturl='/'): - pass - - @abstractmethod - def get_access_token_by_code(self, code): - pass - - @abstractmethod - def get_oauth_userinfo(self): - pass - - @abstractmethod - def get_picture(self, metadata): - pass - - def do_get(self, url, params, headers=None): - rsp = requests.get(url=url, params=params, headers=headers) - logger.info(rsp.text) - return rsp.text - - def do_post(self, url, params, headers=None): - rsp = requests.post(url, params, headers=headers) - logger.info(rsp.text) - return rsp.text - - def get_config(self): - value = OAuthConfig.objects.filter(type=self.ICON_NAME) - return value[0] if value else None - - -class WBOauthManager(BaseOauthManager): - AUTH_URL = 'https://api.weibo.com/oauth2/authorize' - TOKEN_URL = 'https://api.weibo.com/oauth2/access_token' - API_URL = 'https://api.weibo.com/2/users/show.json' - ICON_NAME = 'weibo' - - def __init__(self, access_token=None, openid=None): - config = self.get_config() - self.client_id = config.appkey if config else '' - self.client_secret = config.appsecret if config else '' - self.callback_url = config.callback_url if config else '' - super( - WBOauthManager, - self).__init__( - access_token=access_token, - openid=openid) - - def get_authorization_url(self, nexturl='/'): - params = { - 'client_id': self.client_id, - 'response_type': 'code', - 'redirect_uri': self.callback_url + '&next_url=' + nexturl - } - url = self.AUTH_URL + "?" + urllib.parse.urlencode(params) - return url - - def get_access_token_by_code(self, code): - - params = { - 'client_id': self.client_id, - 'client_secret': self.client_secret, - 'grant_type': 'authorization_code', - 'code': code, - 'redirect_uri': self.callback_url - } - rsp = self.do_post(self.TOKEN_URL, params) - - obj = json.loads(rsp) - if 'access_token' in obj: - self.access_token = str(obj['access_token']) - self.openid = str(obj['uid']) - return self.get_oauth_userinfo() - else: - raise OAuthAccessTokenException(rsp) - - def get_oauth_userinfo(self): - if not self.is_authorized: - return None - params = { - 'uid': self.openid, - 'access_token': self.access_token - } - rsp = self.do_get(self.API_URL, params) - try: - datas = json.loads(rsp) - user = OAuthUser() - user.metadata = rsp - user.picture = datas['avatar_large'] - user.nickname = datas['screen_name'] - user.openid = datas['id'] - user.type = 'weibo' - user.token = self.access_token - if 'email' in datas and datas['email']: - user.email = datas['email'] - return user - except Exception as e: - logger.error(e) - logger.error('weibo oauth error.rsp:' + rsp) - return None - - def get_picture(self, metadata): - datas = json.loads(metadata) - return datas['avatar_large'] - - -class ProxyManagerMixin: - def __init__(self, *args, **kwargs): - if os.environ.get("HTTP_PROXY"): - self.proxies = { - "http": os.environ.get("HTTP_PROXY"), - "https": os.environ.get("HTTP_PROXY") - } - else: - self.proxies = None - - def do_get(self, url, params, headers=None): - rsp = requests.get(url=url, params=params, headers=headers, proxies=self.proxies) - logger.info(rsp.text) - return rsp.text - - def do_post(self, url, params, headers=None): - rsp = requests.post(url, params, headers=headers, proxies=self.proxies) - logger.info(rsp.text) - return rsp.text - - -class GoogleOauthManager(ProxyManagerMixin, BaseOauthManager): - AUTH_URL = 'https://accounts.google.com/o/oauth2/v2/auth' - TOKEN_URL = 'https://www.googleapis.com/oauth2/v4/token' - API_URL = 'https://www.googleapis.com/oauth2/v3/userinfo' - ICON_NAME = 'google' - - def __init__(self, access_token=None, openid=None): - config = self.get_config() - self.client_id = config.appkey if config else '' - self.client_secret = config.appsecret if config else '' - self.callback_url = config.callback_url if config else '' - super( - GoogleOauthManager, - self).__init__( - access_token=access_token, - openid=openid) - - def get_authorization_url(self, nexturl='/'): - params = { - 'client_id': self.client_id, - 'response_type': 'code', - 'redirect_uri': self.callback_url, - 'scope': 'openid email', - } - url = self.AUTH_URL + "?" + urllib.parse.urlencode(params) - return url - - def get_access_token_by_code(self, code): - params = { - 'client_id': self.client_id, - 'client_secret': self.client_secret, - 'grant_type': 'authorization_code', - 'code': code, - - 'redirect_uri': self.callback_url - } - rsp = self.do_post(self.TOKEN_URL, params) - - obj = json.loads(rsp) - - if 'access_token' in obj: - self.access_token = str(obj['access_token']) - self.openid = str(obj['id_token']) - logger.info(self.ICON_NAME + ' oauth ' + rsp) - return self.access_token - else: - raise OAuthAccessTokenException(rsp) - - def get_oauth_userinfo(self): - if not self.is_authorized: - return None - params = { - 'access_token': self.access_token - } - rsp = self.do_get(self.API_URL, params) - try: - - datas = json.loads(rsp) - user = OAuthUser() - user.metadata = rsp - user.picture = datas['picture'] - user.nickname = datas['name'] - user.openid = datas['sub'] - user.token = self.access_token - user.type = 'google' - if datas['email']: - user.email = datas['email'] - return user - except Exception as e: - logger.error(e) - logger.error('google oauth error.rsp:' + rsp) - return None - - def get_picture(self, metadata): - datas = json.loads(metadata) - return datas['picture'] - - -class GitHubOauthManager(ProxyManagerMixin, BaseOauthManager): - AUTH_URL = 'https://github.com/login/oauth/authorize' - TOKEN_URL = 'https://github.com/login/oauth/access_token' - API_URL = 'https://api.github.com/user' - ICON_NAME = 'github' - - def __init__(self, access_token=None, openid=None): - config = self.get_config() - self.client_id = config.appkey if config else '' - self.client_secret = config.appsecret if config else '' - self.callback_url = config.callback_url if config else '' - super( - GitHubOauthManager, - self).__init__( - access_token=access_token, - openid=openid) - - def get_authorization_url(self, next_url='/'): - params = { - 'client_id': self.client_id, - 'response_type': 'code', - 'redirect_uri': f'{self.callback_url}&next_url={next_url}', - 'scope': 'user' - } - url = self.AUTH_URL + "?" + urllib.parse.urlencode(params) - return url - - def get_access_token_by_code(self, code): - params = { - 'client_id': self.client_id, - 'client_secret': self.client_secret, - 'grant_type': 'authorization_code', - 'code': code, - - 'redirect_uri': self.callback_url - } - rsp = self.do_post(self.TOKEN_URL, params) - - from urllib import parse - r = parse.parse_qs(rsp) - if 'access_token' in r: - self.access_token = (r['access_token'][0]) - return self.access_token - else: - raise OAuthAccessTokenException(rsp) - - def get_oauth_userinfo(self): - - rsp = self.do_get(self.API_URL, params={}, headers={ - "Authorization": "token " + self.access_token - }) - try: - datas = json.loads(rsp) - user = OAuthUser() - user.picture = datas['avatar_url'] - user.nickname = datas['name'] - user.openid = datas['id'] - user.type = 'github' - user.token = self.access_token - user.metadata = rsp - if 'email' in datas and datas['email']: - user.email = datas['email'] - return user - except Exception as e: - logger.error(e) - logger.error('github oauth error.rsp:' + rsp) - return None - - def get_picture(self, metadata): - datas = json.loads(metadata) - return datas['avatar_url'] - - -class FaceBookOauthManager(ProxyManagerMixin, BaseOauthManager): - AUTH_URL = 'https://www.facebook.com/v16.0/dialog/oauth' - TOKEN_URL = 'https://graph.facebook.com/v16.0/oauth/access_token' - API_URL = 'https://graph.facebook.com/me' - ICON_NAME = 'facebook' - - def __init__(self, access_token=None, openid=None): - config = self.get_config() - self.client_id = config.appkey if config else '' - self.client_secret = config.appsecret if config else '' - self.callback_url = config.callback_url if config else '' - super( - FaceBookOauthManager, - self).__init__( - access_token=access_token, - openid=openid) - - def get_authorization_url(self, next_url='/'): - params = { - 'client_id': self.client_id, - 'response_type': 'code', - 'redirect_uri': self.callback_url, - 'scope': 'email,public_profile' - } - url = self.AUTH_URL + "?" + urllib.parse.urlencode(params) - return url - - def get_access_token_by_code(self, code): - params = { - 'client_id': self.client_id, - 'client_secret': self.client_secret, - # 'grant_type': 'authorization_code', - 'code': code, - - 'redirect_uri': self.callback_url - } - rsp = self.do_post(self.TOKEN_URL, params) - - obj = json.loads(rsp) - if 'access_token' in obj: - token = str(obj['access_token']) - self.access_token = token - return self.access_token - else: - raise OAuthAccessTokenException(rsp) - - def get_oauth_userinfo(self): - params = { - 'access_token': self.access_token, - 'fields': 'id,name,picture,email' - } - try: - rsp = self.do_get(self.API_URL, params) - datas = json.loads(rsp) - user = OAuthUser() - user.nickname = datas['name'] - user.openid = datas['id'] - user.type = 'facebook' - user.token = self.access_token - user.metadata = rsp - if 'email' in datas and datas['email']: - user.email = datas['email'] - if 'picture' in datas and datas['picture'] and datas['picture']['data'] and datas['picture']['data']['url']: - user.picture = str(datas['picture']['data']['url']) - return user - except Exception as e: - logger.error(e) - return None - - def get_picture(self, metadata): - datas = json.loads(metadata) - return str(datas['picture']['data']['url']) - - -class QQOauthManager(BaseOauthManager): - AUTH_URL = 'https://graph.qq.com/oauth2.0/authorize' - TOKEN_URL = 'https://graph.qq.com/oauth2.0/token' - API_URL = 'https://graph.qq.com/user/get_user_info' - OPEN_ID_URL = 'https://graph.qq.com/oauth2.0/me' - ICON_NAME = 'qq' - - def __init__(self, access_token=None, openid=None): - config = self.get_config() - self.client_id = config.appkey if config else '' - self.client_secret = config.appsecret if config else '' - self.callback_url = config.callback_url if config else '' - super( - QQOauthManager, - self).__init__( - access_token=access_token, - openid=openid) - - def get_authorization_url(self, next_url='/'): - params = { - 'response_type': 'code', - 'client_id': self.client_id, - 'redirect_uri': self.callback_url + '&next_url=' + next_url, - } - url = self.AUTH_URL + "?" + urllib.parse.urlencode(params) - return url - - def get_access_token_by_code(self, code): - params = { - 'grant_type': 'authorization_code', - 'client_id': self.client_id, - 'client_secret': self.client_secret, - 'code': code, - 'redirect_uri': self.callback_url - } - rsp = self.do_get(self.TOKEN_URL, params) - if rsp: - d = urllib.parse.parse_qs(rsp) - if 'access_token' in d: - token = d['access_token'] - self.access_token = token[0] - return token - else: - raise OAuthAccessTokenException(rsp) - - def get_open_id(self): - if self.is_access_token_set: - params = { - 'access_token': self.access_token - } - rsp = self.do_get(self.OPEN_ID_URL, params) - if rsp: - rsp = rsp.replace( - 'callback(', '').replace( - ')', '').replace( - ';', '') - obj = json.loads(rsp) - openid = str(obj['openid']) - self.openid = openid - return openid - - def get_oauth_userinfo(self): - openid = self.get_open_id() - if openid: - params = { - 'access_token': self.access_token, - 'oauth_consumer_key': self.client_id, - 'openid': self.openid - } - rsp = self.do_get(self.API_URL, params) - logger.info(rsp) - obj = json.loads(rsp) - user = OAuthUser() - user.nickname = obj['nickname'] - user.openid = openid - user.type = 'qq' - user.token = self.access_token - user.metadata = rsp - if 'email' in obj: - user.email = obj['email'] - if 'figureurl' in obj: - user.picture = str(obj['figureurl']) - return user - - def get_picture(self, metadata): - datas = json.loads(metadata) - return str(datas['figureurl']) - - -@cache_decorator(expiration=100 * 60) -def get_oauth_apps(): - configs = OAuthConfig.objects.filter(is_enable=True).all() - if not configs: - return [] - configtypes = [x.type for x in configs] - applications = BaseOauthManager.__subclasses__() - apps = [x() for x in applications if x().ICON_NAME.lower() in configtypes] - return apps - - -def get_manager_by_type(type): - applications = get_oauth_apps() - if applications: - finds = list( - filter( - lambda x: x.ICON_NAME.lower() == type.lower(), - applications)) - if finds: - return finds[0] - return None diff --git a/src/DjangoBlog/oauth/templatetags/__init__.py b/src/DjangoBlog/oauth/templatetags/__init__.py deleted file mode 100644 index 8b13789..0000000 --- a/src/DjangoBlog/oauth/templatetags/__init__.py +++ /dev/null @@ -1 +0,0 @@ - diff --git a/src/DjangoBlog/oauth/templatetags/oauth_tags.py b/src/DjangoBlog/oauth/templatetags/oauth_tags.py deleted file mode 100644 index 7b687d5..0000000 --- a/src/DjangoBlog/oauth/templatetags/oauth_tags.py +++ /dev/null @@ -1,22 +0,0 @@ -from django import template -from django.urls import reverse - -from oauth.oauthmanager import get_oauth_apps - -register = template.Library() - - -@register.inclusion_tag('oauth/oauth_applications.html') -def load_oauth_applications(request): - applications = get_oauth_apps() - if applications: - baseurl = reverse('oauth:oauthlogin') - path = request.get_full_path() - - apps = list(map(lambda x: (x.ICON_NAME, '{baseurl}?type={type}&next_url={next}'.format( - baseurl=baseurl, type=x.ICON_NAME, next=path)), applications)) - else: - apps = [] - return { - 'apps': apps - } diff --git a/src/DjangoBlog/oauth/tests.py b/src/DjangoBlog/oauth/tests.py deleted file mode 100644 index bb23b9b..0000000 --- a/src/DjangoBlog/oauth/tests.py +++ /dev/null @@ -1,249 +0,0 @@ -import json -from unittest.mock import patch - -from django.conf import settings -from django.contrib import auth -from django.test import Client, RequestFactory, TestCase -from django.urls import reverse - -from djangoblog.utils import get_sha256 -from oauth.models import OAuthConfig -from oauth.oauthmanager import BaseOauthManager - - -# Create your tests here. -class OAuthConfigTest(TestCase): - def setUp(self): - self.client = Client() - self.factory = RequestFactory() - - def test_oauth_login_test(self): - c = OAuthConfig() - c.type = 'weibo' - c.appkey = 'appkey' - c.appsecret = 'appsecret' - c.save() - - response = self.client.get('/oauth/oauthlogin?type=weibo') - self.assertEqual(response.status_code, 302) - self.assertTrue("api.weibo.com" in response.url) - - response = self.client.get('/oauth/authorize?type=weibo&code=code') - self.assertEqual(response.status_code, 302) - self.assertEqual(response.url, '/') - - -class OauthLoginTest(TestCase): - def setUp(self) -> None: - self.client = Client() - self.factory = RequestFactory() - self.apps = self.init_apps() - - def init_apps(self): - applications = [p() for p in BaseOauthManager.__subclasses__()] - for application in applications: - c = OAuthConfig() - c.type = application.ICON_NAME.lower() - c.appkey = 'appkey' - c.appsecret = 'appsecret' - c.save() - return applications - - def get_app_by_type(self, type): - for app in self.apps: - if app.ICON_NAME.lower() == type: - return app - - @patch("oauth.oauthmanager.WBOauthManager.do_post") - @patch("oauth.oauthmanager.WBOauthManager.do_get") - def test_weibo_login(self, mock_do_get, mock_do_post): - weibo_app = self.get_app_by_type('weibo') - assert weibo_app - url = weibo_app.get_authorization_url() - mock_do_post.return_value = json.dumps({"access_token": "access_token", - "uid": "uid" - }) - mock_do_get.return_value = json.dumps({ - "avatar_large": "avatar_large", - "screen_name": "screen_name", - "id": "id", - "email": "email", - }) - userinfo = weibo_app.get_access_token_by_code('code') - self.assertEqual(userinfo.token, 'access_token') - self.assertEqual(userinfo.openid, 'id') - - @patch("oauth.oauthmanager.GoogleOauthManager.do_post") - @patch("oauth.oauthmanager.GoogleOauthManager.do_get") - def test_google_login(self, mock_do_get, mock_do_post): - google_app = self.get_app_by_type('google') - assert google_app - url = google_app.get_authorization_url() - mock_do_post.return_value = json.dumps({ - "access_token": "access_token", - "id_token": "id_token", - }) - mock_do_get.return_value = json.dumps({ - "picture": "picture", - "name": "name", - "sub": "sub", - "email": "email", - }) - token = google_app.get_access_token_by_code('code') - userinfo = google_app.get_oauth_userinfo() - self.assertEqual(userinfo.token, 'access_token') - self.assertEqual(userinfo.openid, 'sub') - - @patch("oauth.oauthmanager.GitHubOauthManager.do_post") - @patch("oauth.oauthmanager.GitHubOauthManager.do_get") - def test_github_login(self, mock_do_get, mock_do_post): - github_app = self.get_app_by_type('github') - assert github_app - url = github_app.get_authorization_url() - self.assertTrue("github.com" in url) - self.assertTrue("client_id" in url) - mock_do_post.return_value = "access_token=gho_16C7e42F292c6912E7710c838347Ae178B4a&scope=repo%2Cgist&token_type=bearer" - mock_do_get.return_value = json.dumps({ - "avatar_url": "avatar_url", - "name": "name", - "id": "id", - "email": "email", - }) - token = github_app.get_access_token_by_code('code') - userinfo = github_app.get_oauth_userinfo() - self.assertEqual(userinfo.token, 'gho_16C7e42F292c6912E7710c838347Ae178B4a') - self.assertEqual(userinfo.openid, 'id') - - @patch("oauth.oauthmanager.FaceBookOauthManager.do_post") - @patch("oauth.oauthmanager.FaceBookOauthManager.do_get") - def test_facebook_login(self, mock_do_get, mock_do_post): - facebook_app = self.get_app_by_type('facebook') - assert facebook_app - url = facebook_app.get_authorization_url() - self.assertTrue("facebook.com" in url) - mock_do_post.return_value = json.dumps({ - "access_token": "access_token", - }) - mock_do_get.return_value = json.dumps({ - "name": "name", - "id": "id", - "email": "email", - "picture": { - "data": { - "url": "url" - } - } - }) - token = facebook_app.get_access_token_by_code('code') - userinfo = facebook_app.get_oauth_userinfo() - self.assertEqual(userinfo.token, 'access_token') - - @patch("oauth.oauthmanager.QQOauthManager.do_get", side_effect=[ - 'access_token=access_token&expires_in=3600', - 'callback({"client_id":"appid","openid":"openid"} );', - json.dumps({ - "nickname": "nickname", - "email": "email", - "figureurl": "figureurl", - "openid": "openid", - }) - ]) - def test_qq_login(self, mock_do_get): - qq_app = self.get_app_by_type('qq') - assert qq_app - url = qq_app.get_authorization_url() - self.assertTrue("qq.com" in url) - token = qq_app.get_access_token_by_code('code') - userinfo = qq_app.get_oauth_userinfo() - self.assertEqual(userinfo.token, 'access_token') - - @patch("oauth.oauthmanager.WBOauthManager.do_post") - @patch("oauth.oauthmanager.WBOauthManager.do_get") - def test_weibo_authoriz_login_with_email(self, mock_do_get, mock_do_post): - - mock_do_post.return_value = json.dumps({"access_token": "access_token", - "uid": "uid" - }) - mock_user_info = { - "avatar_large": "avatar_large", - "screen_name": "screen_name1", - "id": "id", - "email": "email", - } - mock_do_get.return_value = json.dumps(mock_user_info) - - response = self.client.get('/oauth/oauthlogin?type=weibo') - self.assertEqual(response.status_code, 302) - self.assertTrue("api.weibo.com" in response.url) - - response = self.client.get('/oauth/authorize?type=weibo&code=code') - self.assertEqual(response.status_code, 302) - self.assertEqual(response.url, '/') - - user = auth.get_user(self.client) - assert user.is_authenticated - self.assertTrue(user.is_authenticated) - self.assertEqual(user.username, mock_user_info['screen_name']) - self.assertEqual(user.email, mock_user_info['email']) - self.client.logout() - - response = self.client.get('/oauth/authorize?type=weibo&code=code') - self.assertEqual(response.status_code, 302) - self.assertEqual(response.url, '/') - - user = auth.get_user(self.client) - assert user.is_authenticated - self.assertTrue(user.is_authenticated) - self.assertEqual(user.username, mock_user_info['screen_name']) - self.assertEqual(user.email, mock_user_info['email']) - - @patch("oauth.oauthmanager.WBOauthManager.do_post") - @patch("oauth.oauthmanager.WBOauthManager.do_get") - def test_weibo_authoriz_login_without_email(self, mock_do_get, mock_do_post): - - mock_do_post.return_value = json.dumps({"access_token": "access_token", - "uid": "uid" - }) - mock_user_info = { - "avatar_large": "avatar_large", - "screen_name": "screen_name1", - "id": "id", - } - mock_do_get.return_value = json.dumps(mock_user_info) - - response = self.client.get('/oauth/oauthlogin?type=weibo') - self.assertEqual(response.status_code, 302) - self.assertTrue("api.weibo.com" in response.url) - - response = self.client.get('/oauth/authorize?type=weibo&code=code') - - self.assertEqual(response.status_code, 302) - - oauth_user_id = int(response.url.split('/')[-1].split('.')[0]) - self.assertEqual(response.url, f'/oauth/requireemail/{oauth_user_id}.html') - - response = self.client.post(response.url, {'email': 'test@gmail.com', 'oauthid': oauth_user_id}) - - self.assertEqual(response.status_code, 302) - sign = get_sha256(settings.SECRET_KEY + - str(oauth_user_id) + settings.SECRET_KEY) - - url = reverse('oauth:bindsuccess', kwargs={ - 'oauthid': oauth_user_id, - }) - self.assertEqual(response.url, f'{url}?type=email') - - path = reverse('oauth:email_confirm', kwargs={ - 'id': oauth_user_id, - 'sign': sign - }) - response = self.client.get(path) - self.assertEqual(response.status_code, 302) - self.assertEqual(response.url, f'/oauth/bindsuccess/{oauth_user_id}.html?type=success') - user = auth.get_user(self.client) - from oauth.models import OAuthUser - oauth_user = OAuthUser.objects.get(author=user) - self.assertTrue(user.is_authenticated) - self.assertEqual(user.username, mock_user_info['screen_name']) - self.assertEqual(user.email, 'test@gmail.com') - self.assertEqual(oauth_user.pk, oauth_user_id) diff --git a/src/DjangoBlog/oauth/urls.py b/src/DjangoBlog/oauth/urls.py deleted file mode 100644 index c4a12a0..0000000 --- a/src/DjangoBlog/oauth/urls.py +++ /dev/null @@ -1,25 +0,0 @@ -from django.urls import path - -from . import views - -app_name = "oauth" -urlpatterns = [ - path( - r'oauth/authorize', - views.authorize), - path( - r'oauth/requireemail/.html', - views.RequireEmailView.as_view(), - name='require_email'), - path( - r'oauth/emailconfirm//.html', - views.emailconfirm, - name='email_confirm'), - path( - r'oauth/bindsuccess/.html', - views.bindsuccess, - name='bindsuccess'), - path( - r'oauth/oauthlogin', - views.oauthlogin, - name='oauthlogin')] diff --git a/src/DjangoBlog/oauth/views.py b/src/DjangoBlog/oauth/views.py deleted file mode 100644 index 12e3a6e..0000000 --- a/src/DjangoBlog/oauth/views.py +++ /dev/null @@ -1,253 +0,0 @@ -import logging -# Create your views here. -from urllib.parse import urlparse - -from django.conf import settings -from django.contrib.auth import get_user_model -from django.contrib.auth import login -from django.core.exceptions import ObjectDoesNotExist -from django.db import transaction -from django.http import HttpResponseForbidden -from django.http import HttpResponseRedirect -from django.shortcuts import get_object_or_404 -from django.shortcuts import render -from django.urls import reverse -from django.utils import timezone -from django.utils.translation import gettext_lazy as _ -from django.views.generic import FormView - -from djangoblog.blog_signals import oauth_user_login_signal -from djangoblog.utils import get_current_site -from djangoblog.utils import send_email, get_sha256 -from oauth.forms import RequireEmailForm -from .models import OAuthUser -from .oauthmanager import get_manager_by_type, OAuthAccessTokenException - -logger = logging.getLogger(__name__) - - -def get_redirecturl(request): - nexturl = request.GET.get('next_url', None) - if not nexturl or nexturl == '/login/' or nexturl == '/login': - nexturl = '/' - return nexturl - p = urlparse(nexturl) - if p.netloc: - site = get_current_site().domain - if not p.netloc.replace('www.', '') == site.replace('www.', ''): - logger.info('非法url:' + nexturl) - return "/" - return nexturl - - -def oauthlogin(request): - type = request.GET.get('type', None) - if not type: - return HttpResponseRedirect('/') - manager = get_manager_by_type(type) - if not manager: - return HttpResponseRedirect('/') - nexturl = get_redirecturl(request) - authorizeurl = manager.get_authorization_url(nexturl) - return HttpResponseRedirect(authorizeurl) - - -def authorize(request): - type = request.GET.get('type', None) - if not type: - return HttpResponseRedirect('/') - manager = get_manager_by_type(type) - if not manager: - return HttpResponseRedirect('/') - code = request.GET.get('code', None) - try: - rsp = manager.get_access_token_by_code(code) - except OAuthAccessTokenException as e: - logger.warning("OAuthAccessTokenException:" + str(e)) - return HttpResponseRedirect('/') - except Exception as e: - logger.error(e) - rsp = None - nexturl = get_redirecturl(request) - if not rsp: - 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: - temp = OAuthUser.objects.get(type=type, openid=user.openid) - temp.picture = user.picture - temp.metadata = user.metadata - temp.nickname = user.nickname - user = temp - except ObjectDoesNotExist: - pass - # 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]: - try: - get_user_model().objects.get(username=user.nickname) - except ObjectDoesNotExist: - author.username = user.nickname - else: - author.username = "djangoblog" + timezone.now().strftime('%y%m%d%I%M%S') - author.source = 'authorize' - author.save() - - 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 - }) - - return HttpResponseRedirect(url) - 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(): - return HttpResponseForbidden() - oauthuser = get_object_or_404(OAuthUser, pk=id) - with transaction.atomic(): - if oauthuser.author: - author = get_user_model().objects.get(pk=oauthuser.author_id) - else: - result = get_user_model().objects.get_or_create(email=oauthuser.email) - author = result[0] - if result[1]: - author.source = 'emailconfirm' - author.username = oauthuser.nickname.strip() if oauthuser.nickname.strip( - ) else "djangoblog" + timezone.now().strftime('%y%m%d%I%M%S') - 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 - %(oauthuser_type)s to directly log in to this website without a password.

- You are welcome to continue to follow this site, the address is - %(site)s - Thank you again! -
- If the link above cannot be opened, please copy this link to your browser. - %(site)s - ''') % {'oauthuser_type': oauthuser.type, 'site': site} - - send_email(emailto=[oauthuser.email, ], title=_('Congratulations on your successful binding!'), content=content) - url = reverse('oauth:bindsuccess', kwargs={ - 'oauthid': id - }) - url = url + '?type=success' - return HttpResponseRedirect(url) - - -class RequireEmailView(FormView): - form_class = RequireEmailForm - template_name = 'oauth/require_email.html' - - def get(self, request, *args, **kwargs): - oauthid = self.kwargs['oauthid'] - oauthuser = get_object_or_404(OAuthUser, pk=oauthid) - if oauthuser.email: - pass - # return HttpResponseRedirect('/') - - return super(RequireEmailView, self).get(request, *args, **kwargs) - - def get_initial(self): - oauthid = self.kwargs['oauthid'] - return { - 'email': '', - 'oauthid': oauthid - } - - def get_context_data(self, **kwargs): - oauthid = self.kwargs['oauthid'] - oauthuser = get_object_or_404(OAuthUser, pk=oauthid) - if oauthuser.picture: - kwargs['picture'] = oauthuser.picture - return super(RequireEmailView, self).get_context_data(**kwargs) - - def form_valid(self, form): - email = form.cleaned_data['email'] - oauthid = form.cleaned_data['oauthid'] - oauthuser = get_object_or_404(OAuthUser, pk=oauthid) - oauthuser.email = email - oauthuser.save() - sign = get_sha256(settings.SECRET_KEY + - str(oauthuser.id) + settings.SECRET_KEY) - site = get_current_site().domain - if settings.DEBUG: - site = '127.0.0.1:8000' - path = reverse('oauth:email_confirm', kwargs={ - 'id': oauthid, - 'sign': sign - }) - url = "http://{site}{path}".format(site=site, path=path) - - content = _(""" -

Please click the link below to bind your email

- - %(url)s - - Thank you again! -
- If the link above cannot be opened, please copy this link to your browser. -
- %(url)s - """) % {'url': url} - send_email(emailto=[email, ], title=_('Bind your email'), content=content) - url = reverse('oauth:bindsuccess', kwargs={ - 'oauthid': oauthid - }) - url = url + '?type=email' - return HttpResponseRedirect(url) - - -def bindsuccess(request, oauthid): - type = request.GET.get('type', None) - oauthuser = get_object_or_404(OAuthUser, pk=oauthid) - if type == 'email': - title = _('Bind your email') - content = _( - 'Congratulations, the binding is just one step away. ' - 'Please log in to your email to check the email to complete the binding. Thank you.') - else: - title = _('Binding successful') - content = _( - "Congratulations, you have successfully bound your email address. You can use %(oauthuser_type)s" - " to directly log in to this website without a password. You are welcome to continue to follow this site." % { - 'oauthuser_type': oauthuser.type}) - return render(request, 'oauth/bindsuccess.html', { - 'title': title, - 'content': content - }) diff --git a/src/DjangoBlog/owntracks/__init__.py b/src/DjangoBlog/owntracks/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/src/DjangoBlog/owntracks/admin.py b/src/DjangoBlog/owntracks/admin.py deleted file mode 100644 index 655b535..0000000 --- a/src/DjangoBlog/owntracks/admin.py +++ /dev/null @@ -1,7 +0,0 @@ -from django.contrib import admin - -# Register your models here. - - -class OwnTrackLogsAdmin(admin.ModelAdmin): - pass diff --git a/src/DjangoBlog/owntracks/apps.py b/src/DjangoBlog/owntracks/apps.py deleted file mode 100644 index 1bc5f12..0000000 --- a/src/DjangoBlog/owntracks/apps.py +++ /dev/null @@ -1,5 +0,0 @@ -from django.apps import AppConfig - - -class OwntracksConfig(AppConfig): - name = 'owntracks' diff --git a/src/DjangoBlog/owntracks/migrations/0001_initial.py b/src/DjangoBlog/owntracks/migrations/0001_initial.py deleted file mode 100644 index 9eee55c..0000000 --- a/src/DjangoBlog/owntracks/migrations/0001_initial.py +++ /dev/null @@ -1,31 +0,0 @@ -# Generated by Django 4.1.7 on 2023-03-02 07:14 - -from django.db import migrations, models -import django.utils.timezone - - -class Migration(migrations.Migration): - - initial = True - - dependencies = [ - ] - - operations = [ - migrations.CreateModel( - name='OwnTrackLog', - fields=[ - ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('tid', models.CharField(max_length=100, verbose_name='用户')), - ('lat', models.FloatField(verbose_name='纬度')), - ('lon', models.FloatField(verbose_name='经度')), - ('created_time', models.DateTimeField(default=django.utils.timezone.now, verbose_name='创建时间')), - ], - options={ - 'verbose_name': 'OwnTrackLogs', - 'verbose_name_plural': 'OwnTrackLogs', - 'ordering': ['created_time'], - 'get_latest_by': 'created_time', - }, - ), - ] diff --git a/src/DjangoBlog/owntracks/migrations/0002_alter_owntracklog_options_and_more.py b/src/DjangoBlog/owntracks/migrations/0002_alter_owntracklog_options_and_more.py deleted file mode 100644 index b4f8dec..0000000 --- a/src/DjangoBlog/owntracks/migrations/0002_alter_owntracklog_options_and_more.py +++ /dev/null @@ -1,22 +0,0 @@ -# Generated by Django 4.2.5 on 2023-09-06 13:19 - -from django.db import migrations - - -class Migration(migrations.Migration): - - dependencies = [ - ('owntracks', '0001_initial'), - ] - - operations = [ - migrations.AlterModelOptions( - name='owntracklog', - options={'get_latest_by': 'creation_time', 'ordering': ['creation_time'], 'verbose_name': 'OwnTrackLogs', 'verbose_name_plural': 'OwnTrackLogs'}, - ), - migrations.RenameField( - model_name='owntracklog', - old_name='created_time', - new_name='creation_time', - ), - ] diff --git a/src/DjangoBlog/owntracks/migrations/__init__.py b/src/DjangoBlog/owntracks/migrations/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/src/DjangoBlog/owntracks/models.py b/src/DjangoBlog/owntracks/models.py deleted file mode 100644 index 760942c..0000000 --- a/src/DjangoBlog/owntracks/models.py +++ /dev/null @@ -1,20 +0,0 @@ -from django.db import models -from django.utils.timezone import now - - -# Create your models here. - -class OwnTrackLog(models.Model): - tid = models.CharField(max_length=100, null=False, verbose_name='用户') - lat = models.FloatField(verbose_name='纬度') - lon = models.FloatField(verbose_name='经度') - creation_time = models.DateTimeField('创建时间', default=now) - - def __str__(self): - return self.tid - - class Meta: - ordering = ['creation_time'] - verbose_name = "OwnTrackLogs" - verbose_name_plural = verbose_name - get_latest_by = 'creation_time' diff --git a/src/DjangoBlog/owntracks/tests.py b/src/DjangoBlog/owntracks/tests.py deleted file mode 100644 index 3b4b9d8..0000000 --- a/src/DjangoBlog/owntracks/tests.py +++ /dev/null @@ -1,64 +0,0 @@ -import json - -from django.test import Client, RequestFactory, TestCase - -from accounts.models import BlogUser -from .models import OwnTrackLog - - -# Create your tests here. - -class OwnTrackLogTest(TestCase): - def setUp(self): - self.client = Client() - self.factory = RequestFactory() - - def test_own_track_log(self): - o = { - 'tid': 12, - 'lat': 123.123, - 'lon': 134.341 - } - - self.client.post( - '/owntracks/logtracks', - json.dumps(o), - content_type='application/json') - length = len(OwnTrackLog.objects.all()) - self.assertEqual(length, 1) - - o = { - 'tid': 12, - 'lat': 123.123 - } - - self.client.post( - '/owntracks/logtracks', - json.dumps(o), - content_type='application/json') - length = len(OwnTrackLog.objects.all()) - self.assertEqual(length, 1) - - rsp = self.client.get('/owntracks/show_maps') - self.assertEqual(rsp.status_code, 302) - - user = BlogUser.objects.create_superuser( - email="liangliangyy1@gmail.com", - username="liangliangyy1", - password="liangliangyy1") - - self.client.login(username='liangliangyy1', password='liangliangyy1') - s = OwnTrackLog() - s.tid = 12 - s.lon = 123.234 - s.lat = 34.234 - s.save() - - rsp = self.client.get('/owntracks/show_dates') - self.assertEqual(rsp.status_code, 200) - rsp = self.client.get('/owntracks/show_maps') - self.assertEqual(rsp.status_code, 200) - rsp = self.client.get('/owntracks/get_datas') - self.assertEqual(rsp.status_code, 200) - rsp = self.client.get('/owntracks/get_datas?date=2018-02-26') - self.assertEqual(rsp.status_code, 200) diff --git a/src/DjangoBlog/owntracks/urls.py b/src/DjangoBlog/owntracks/urls.py deleted file mode 100644 index c19ada8..0000000 --- a/src/DjangoBlog/owntracks/urls.py +++ /dev/null @@ -1,12 +0,0 @@ -from django.urls import path - -from . import views - -app_name = "owntracks" - -urlpatterns = [ - path('owntracks/logtracks', views.manage_owntrack_log, name='logtracks'), - path('owntracks/show_maps', views.show_maps, name='show_maps'), - path('owntracks/get_datas', views.get_datas, name='get_datas'), - path('owntracks/show_dates', views.show_log_dates, name='show_dates') -] diff --git a/src/DjangoBlog/owntracks/views.py b/src/DjangoBlog/owntracks/views.py deleted file mode 100644 index 4c72bdd..0000000 --- a/src/DjangoBlog/owntracks/views.py +++ /dev/null @@ -1,127 +0,0 @@ -# Create your views here. -import datetime -import itertools -import json -import logging -from datetime import timezone -from itertools import groupby - -import django -import requests -from django.contrib.auth.decorators import login_required -from django.http import HttpResponse -from django.http import JsonResponse -from django.shortcuts import render -from django.views.decorators.csrf import csrf_exempt - -from .models import OwnTrackLog - -logger = logging.getLogger(__name__) - - -@csrf_exempt -def manage_owntrack_log(request): - try: - s = json.loads(request.read().decode('utf-8')) - tid = s['tid'] - lat = s['lat'] - lon = s['lon'] - - logger.info( - 'tid:{tid}.lat:{lat}.lon:{lon}'.format( - tid=tid, lat=lat, lon=lon)) - if tid and lat and lon: - m = OwnTrackLog() - m.tid = tid - m.lat = lat - m.lon = lon - m.save() - return HttpResponse('ok') - else: - return HttpResponse('data error') - except Exception as e: - logger.error(e) - return HttpResponse('error') - - -@login_required -def show_maps(request): - if request.user.is_superuser: - defaultdate = str(datetime.datetime.now(timezone.utc).date()) - date = request.GET.get('date', defaultdate) - context = { - 'date': date - } - return render(request, 'owntracks/show_maps.html', context) - else: - from django.http import HttpResponseForbidden - return HttpResponseForbidden() - - -@login_required -def show_log_dates(request): - dates = OwnTrackLog.objects.values_list('creation_time', flat=True) - results = list(sorted(set(map(lambda x: x.strftime('%Y-%m-%d'), dates)))) - - context = { - 'results': results - } - return render(request, 'owntracks/show_log_dates.html', context) - - -def convert_to_amap(locations): - convert_result = [] - it = iter(locations) - - item = list(itertools.islice(it, 30)) - while item: - datas = ';'.join( - set(map(lambda x: str(x.lon) + ',' + str(x.lat), item))) - - key = '8440a376dfc9743d8924bf0ad141f28e' - api = 'http://restapi.amap.com/v3/assistant/coordinate/convert' - query = { - 'key': key, - 'locations': datas, - 'coordsys': 'gps' - } - rsp = requests.get(url=api, params=query) - result = json.loads(rsp.text) - if "locations" in result: - convert_result.append(result['locations']) - item = list(itertools.islice(it, 30)) - - return ";".join(convert_result) - - -@login_required -def get_datas(request): - now = django.utils.timezone.now().replace(tzinfo=timezone.utc) - querydate = django.utils.timezone.datetime( - now.year, now.month, now.day, 0, 0, 0) - if request.GET.get('date', None): - date = list(map(lambda x: int(x), request.GET.get('date').split('-'))) - querydate = django.utils.timezone.datetime( - date[0], date[1], date[2], 0, 0, 0) - nextdate = querydate + datetime.timedelta(days=1) - models = OwnTrackLog.objects.filter( - creation_time__range=(querydate, nextdate)) - result = list() - if models and len(models): - for tid, item in groupby( - sorted(models, key=lambda k: k.tid), key=lambda k: k.tid): - - d = dict() - d["name"] = tid - paths = list() - # 使用高德转换后的经纬度 - # locations = convert_to_amap( - # sorted(item, key=lambda x: x.creation_time)) - # for i in locations.split(';'): - # paths.append(i.split(',')) - # 使用GPS原始经纬度 - for location in sorted(item, key=lambda x: x.creation_time): - paths.append([str(location.lon), str(location.lat)]) - d["path"] = paths - result.append(d) - return JsonResponse(result, safe=False) diff --git a/src/DjangoBlog/plugins/__init__.py b/src/DjangoBlog/plugins/__init__.py deleted file mode 100644 index e88afca..0000000 --- a/src/DjangoBlog/plugins/__init__.py +++ /dev/null @@ -1 +0,0 @@ -# This file makes this a Python package diff --git a/src/DjangoBlog/plugins/article_copyright/__init__.py b/src/DjangoBlog/plugins/article_copyright/__init__.py deleted file mode 100644 index e88afca..0000000 --- a/src/DjangoBlog/plugins/article_copyright/__init__.py +++ /dev/null @@ -1 +0,0 @@ -# This file makes this a Python package diff --git a/src/DjangoBlog/plugins/article_copyright/plugin.py b/src/DjangoBlog/plugins/article_copyright/plugin.py deleted file mode 100644 index 5dba3b3..0000000 --- a/src/DjangoBlog/plugins/article_copyright/plugin.py +++ /dev/null @@ -1,37 +0,0 @@ -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 ArticleCopyrightPlugin(BasePlugin): - PLUGIN_NAME = '文章结尾版权声明' - PLUGIN_DESCRIPTION = '一个在文章正文末尾添加版权声明的插件。' - PLUGIN_VERSION = '0.2.0' - PLUGIN_AUTHOR = 'liangliangyy' - - # 2. 实现 register_hooks 方法,专门用于注册钩子 - def register_hooks(self): - # 在这里将插件的方法注册到指定的钩子上 - hooks.register(ARTICLE_CONTENT_HOOK_NAME, self.add_copyright_to_content) - - def add_copyright_to_content(self, content, *args, **kwargs): - """ - 这个方法会被注册到 'the_content' 过滤器钩子上。 - 它接收原始内容,并返回添加了版权信息的新内容。 - """ - 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 - - -# 3. 实例化插件。 -# 这会自动调用 BasePlugin.__init__,然后 BasePlugin.__init__ 会调用我们上面定义的 register_hooks 方法。 -plugin = ArticleCopyrightPlugin() diff --git a/src/DjangoBlog/plugins/article_recommendation/__init__.py b/src/DjangoBlog/plugins/article_recommendation/__init__.py deleted file mode 100644 index 951f2ff..0000000 --- a/src/DjangoBlog/plugins/article_recommendation/__init__.py +++ /dev/null @@ -1 +0,0 @@ -# 文章推荐插件 diff --git a/src/DjangoBlog/plugins/article_recommendation/plugin.py b/src/DjangoBlog/plugins/article_recommendation/plugin.py deleted file mode 100644 index 6656a07..0000000 --- a/src/DjangoBlog/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/DjangoBlog/plugins/article_recommendation/static/article_recommendation/css/recommendation.css b/src/DjangoBlog/plugins/article_recommendation/static/article_recommendation/css/recommendation.css deleted file mode 100644 index b223f41..0000000 --- a/src/DjangoBlog/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/DjangoBlog/plugins/article_recommendation/static/article_recommendation/js/recommendation.js b/src/DjangoBlog/plugins/article_recommendation/static/article_recommendation/js/recommendation.js deleted file mode 100644 index eb19211..0000000 --- a/src/DjangoBlog/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/DjangoBlog/plugins/external_links/__init__.py b/src/DjangoBlog/plugins/external_links/__init__.py deleted file mode 100644 index e88afca..0000000 --- a/src/DjangoBlog/plugins/external_links/__init__.py +++ /dev/null @@ -1 +0,0 @@ -# This file makes this a Python package diff --git a/src/DjangoBlog/plugins/external_links/plugin.py b/src/DjangoBlog/plugins/external_links/plugin.py deleted file mode 100644 index 5b2ef14..0000000 --- a/src/DjangoBlog/plugins/external_links/plugin.py +++ /dev/null @@ -1,48 +0,0 @@ -import re -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 ExternalLinksPlugin(BasePlugin): - PLUGIN_NAME = '外部链接处理器' - PLUGIN_DESCRIPTION = '自动为文章中的外部链接添加 target="_blank" 和 rel="noopener noreferrer" 属性。' - PLUGIN_VERSION = '0.1.0' - PLUGIN_AUTHOR = 'liangliangyy' - - def register_hooks(self): - hooks.register(ARTICLE_CONTENT_HOOK_NAME, self.process_external_links) - - def process_external_links(self, content, *args, **kwargs): - from djangoblog.utils import get_current_site - site_domain = get_current_site().domain - - # 正则表达式查找所有 标签 - link_pattern = re.compile(r'(]*?\s+)?href=")([^"]*)(".*?/a>)', re.IGNORECASE) - - def replacer(match): - # match.group(1) 是 ... - href = match.group(2) - - # 如果链接已经有 target 属性,则不处理 - if 'target=' in match.group(0).lower(): - return match.group(0) - - # 解析链接 - parsed_url = urlparse(href) - - # 如果链接是外部的 (有域名且域名不等于当前网站域名) - if parsed_url.netloc and parsed_url.netloc != site_domain: - # 添加 target 和 rel 属性 - return f'{match.group(1)}{href}" target="_blank" rel="noopener noreferrer"{match.group(3)}' - - # 否则返回原样 - return match.group(0) - - return link_pattern.sub(replacer, content) - - -plugin = ExternalLinksPlugin() diff --git a/src/DjangoBlog/plugins/image_lazy_loading/__init__.py b/src/DjangoBlog/plugins/image_lazy_loading/__init__.py deleted file mode 100644 index 2d27de0..0000000 --- a/src/DjangoBlog/plugins/image_lazy_loading/__init__.py +++ /dev/null @@ -1 +0,0 @@ -# Image Lazy Loading Plugin diff --git a/src/DjangoBlog/plugins/image_lazy_loading/plugin.py b/src/DjangoBlog/plugins/image_lazy_loading/plugin.py deleted file mode 100644 index b4b9e0a..0000000 --- a/src/DjangoBlog/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/DjangoBlog/plugins/reading_time/__init__.py b/src/DjangoBlog/plugins/reading_time/__init__.py deleted file mode 100644 index e88afca..0000000 --- a/src/DjangoBlog/plugins/reading_time/__init__.py +++ /dev/null @@ -1 +0,0 @@ -# This file makes this a Python package diff --git a/src/DjangoBlog/plugins/reading_time/plugin.py b/src/DjangoBlog/plugins/reading_time/plugin.py deleted file mode 100644 index 4b929d8..0000000 --- a/src/DjangoBlog/plugins/reading_time/plugin.py +++ /dev/null @@ -1,51 +0,0 @@ -import math -import re -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 ReadingTimePlugin(BasePlugin): - PLUGIN_NAME = '阅读时间预测' - PLUGIN_DESCRIPTION = '估算文章阅读时间并显示在文章开头。' - PLUGIN_VERSION = '0.1.0' - PLUGIN_AUTHOR = 'liangliangyy' - - def register_hooks(self): - hooks.register(ARTICLE_CONTENT_HOOK_NAME, self.add_reading_time) - - 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() - - # 中文和英文单词混合计数的一个简单方法 - # 匹配中文字符或连续的非中文字符(视为单词) - words = re.findall(r'[\u4e00-\u9fa5]|\w+', clean_content) - word_count = len(words) - - # 按平均每分钟200字的速度计算 - reading_speed = 200 - reading_minutes = math.ceil(word_count / reading_speed) - - # 如果阅读时间少于1分钟,则显示为1分钟 - if reading_minutes < 1: - reading_minutes = 1 - - reading_time_html = f'

预计阅读时间:{reading_minutes} 分钟

' - - return reading_time_html + content - - -plugin = ReadingTimePlugin() \ No newline at end of file diff --git a/src/DjangoBlog/plugins/seo_optimizer/__init__.py b/src/DjangoBlog/plugins/seo_optimizer/__init__.py deleted file mode 100644 index e88afca..0000000 --- a/src/DjangoBlog/plugins/seo_optimizer/__init__.py +++ /dev/null @@ -1 +0,0 @@ -# This file makes this a Python package diff --git a/src/DjangoBlog/plugins/seo_optimizer/plugin.py b/src/DjangoBlog/plugins/seo_optimizer/plugin.py deleted file mode 100644 index de12c15..0000000 --- a/src/DjangoBlog/plugins/seo_optimizer/plugin.py +++ /dev/null @@ -1,147 +0,0 @@ -import json -from django.utils.html import strip_tags -from django.template.defaultfilters import truncatewords -from djangoblog.plugin_manage.base_plugin import BasePlugin -from djangoblog.plugin_manage import hooks -from blog.models import Article, Category, Tag -from djangoblog.utils import get_blog_setting - - -class SeoOptimizerPlugin(BasePlugin): - PLUGIN_NAME = 'SEO 优化器' - PLUGIN_DESCRIPTION = '为文章、页面等提供 SEO 优化,动态生成 meta 标签和 JSON-LD 结构化数据。' - PLUGIN_VERSION = '0.2.0' - PLUGIN_AUTHOR = 'liuangliangyy' - - def register_hooks(self): - hooks.register('head_meta', self.dispatch_seo_generation) - - def _get_article_seo_data(self, context, request, blog_setting): - article = context.get('article') - if not isinstance(article, Article): - return None - - description = strip_tags(article.body)[:150] - keywords = ",".join([tag.name for tag in article.tags.all()]) or blog_setting.site_keywords - - meta_tags = f''' - - - - - - - - - ''' - for tag in article.tags.all(): - meta_tags += f'' - meta_tags += f'' - - structured_data = { - "@context": "https://schema.org", - "@type": "Article", - "mainEntityOfPage": {"@type": "WebPage", "@id": request.build_absolute_uri()}, - "headline": article.title, - "description": description, - "image": request.build_absolute_uri(article.get_first_image_url()), - "datePublished": article.pub_time.isoformat(), - "dateModified": article.last_modify_time.isoformat(), - "author": {"@type": "Person", "name": article.author.username}, - "publisher": {"@type": "Organization", "name": blog_setting.site_name} - } - if not structured_data.get("image"): - del structured_data["image"] - - return { - "title": f"{article.title} | {blog_setting.site_name}", - "description": description, - "keywords": keywords, - "meta_tags": meta_tags, - "json_ld": structured_data - } - - def _get_category_seo_data(self, context, request, blog_setting): - category_name = context.get('tag_name') - if not category_name: - return None - - category = Category.objects.filter(name=category_name).first() - if not category: - return None - - title = f"{category.name} | {blog_setting.site_name}" - description = strip_tags(category.name) or blog_setting.site_description - keywords = category.name - - # BreadcrumbList structured data for category page - breadcrumb_items = [{"@type": "ListItem", "position": 1, "name": "首页", "item": request.build_absolute_uri('/')}] - breadcrumb_items.append({"@type": "ListItem", "position": 2, "name": category.name, "item": request.build_absolute_uri()}) - - structured_data = { - "@context": "https://schema.org", - "@type": "BreadcrumbList", - "itemListElement": breadcrumb_items - } - - return { - "title": title, - "description": description, - "keywords": keywords, - "meta_tags": "", - "json_ld": structured_data - } - - def _get_default_seo_data(self, context, request, blog_setting): - # Homepage and other default pages - 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", - "target": f"{request.build_absolute_uri('/search/')}?q={{search_term_string}}", - "query-input": "required name=search_term_string" - } - } - return { - "title": f"{blog_setting.site_name} | {blog_setting.site_description}", - "description": blog_setting.site_description, - "keywords": blog_setting.site_keywords, - "meta_tags": "", - "json_ld": structured_data - } - - def dispatch_seo_generation(self, metas, context): - request = context.get('request') - if not request: - return metas - - view_name = request.resolver_match.view_name - blog_setting = get_blog_setting() - - seo_data = None - if view_name == 'blog:detailbyid': - seo_data = self._get_article_seo_data(context, request, blog_setting) - elif view_name == 'blog:category_detail': - seo_data = self._get_category_seo_data(context, request, blog_setting) - - if not seo_data: - seo_data = self._get_default_seo_data(context, request, blog_setting) - - json_ld_script = f'' - - seo_html = 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/DjangoBlog/plugins/view_count/__init__.py b/src/DjangoBlog/plugins/view_count/__init__.py deleted file mode 100644 index 8804fdf..0000000 --- a/src/DjangoBlog/plugins/view_count/__init__.py +++ /dev/null @@ -1 +0,0 @@ -# This file makes this a Python package \ No newline at end of file diff --git a/src/DjangoBlog/plugins/view_count/plugin.py b/src/DjangoBlog/plugins/view_count/plugin.py deleted file mode 100644 index 15e9d94..0000000 --- a/src/DjangoBlog/plugins/view_count/plugin.py +++ /dev/null @@ -1,18 +0,0 @@ -from djangoblog.plugin_manage.base_plugin import BasePlugin -from djangoblog.plugin_manage import hooks - - -class ViewCountPlugin(BasePlugin): - PLUGIN_NAME = '文章浏览次数统计' - PLUGIN_DESCRIPTION = '统计文章的浏览次数' - PLUGIN_VERSION = '0.1.0' - PLUGIN_AUTHOR = 'liangliangyy' - - def register_hooks(self): - hooks.register('after_article_body_get', self.record_view) - - def record_view(self, article, *args, **kwargs): - article.viewed() - - -plugin = ViewCountPlugin() \ No newline at end of file diff --git a/src/DjangoBlog/requirements.txt b/src/DjangoBlog/requirements.txt deleted file mode 100644 index e5878ab..0000000 Binary files a/src/DjangoBlog/requirements.txt and /dev/null differ diff --git a/src/DjangoBlog/servermanager/MemcacheStorage.py b/src/DjangoBlog/servermanager/MemcacheStorage.py deleted file mode 100644 index 38a7990..0000000 --- a/src/DjangoBlog/servermanager/MemcacheStorage.py +++ /dev/null @@ -1,32 +0,0 @@ -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/DjangoBlog/servermanager/__init__.py b/src/DjangoBlog/servermanager/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/src/DjangoBlog/servermanager/admin.py b/src/DjangoBlog/servermanager/admin.py deleted file mode 100644 index f26f4f6..0000000 --- a/src/DjangoBlog/servermanager/admin.py +++ /dev/null @@ -1,19 +0,0 @@ -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/DjangoBlog/servermanager/api/__init__.py b/src/DjangoBlog/servermanager/api/__init__.py deleted file mode 100644 index 8b13789..0000000 --- a/src/DjangoBlog/servermanager/api/__init__.py +++ /dev/null @@ -1 +0,0 @@ - diff --git a/src/DjangoBlog/servermanager/api/blogapi.py b/src/DjangoBlog/servermanager/api/blogapi.py deleted file mode 100644 index 8a4d6ac..0000000 --- a/src/DjangoBlog/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/DjangoBlog/servermanager/api/commonapi.py b/src/DjangoBlog/servermanager/api/commonapi.py deleted file mode 100644 index 83ad9ff..0000000 --- a/src/DjangoBlog/servermanager/api/commonapi.py +++ /dev/null @@ -1,64 +0,0 @@ -import logging -import os - -import openai - -from servermanager.models import commands - -logger = logging.getLogger(__name__) - -openai.api_key = os.environ.get('OPENAI_API_KEY') -if os.environ.get('HTTP_PROXY'): - openai.proxy = os.environ.get('HTTP_PROXY') - - -class ChatGPT: - - @staticmethod - def chat(prompt): - try: - completion = openai.ChatCompletion.create(model="gpt-3.5-turbo", - messages=[{"role": "user", "content": prompt}]) - return completion.choices[0].message.content - except Exception as e: - logger.error(e) - return "服务器出错了" - - -class CommandHandler: - def __init__(self): - self.commands = commands.objects.all() - - def run(self, title): - """ - 运行命令 - :param title: 命令 - :return: 返回命令执行结果 - """ - cmd = list( - filter( - lambda x: x.title.upper() == title.upper(), - self.commands)) - if cmd: - return self.__run_command__(cmd[0].command) - else: - return "未找到相关命令,请输入hepme获得帮助。" - - def __run_command__(self, cmd): - try: - res = os.popen(cmd).read() - return res - except BaseException: - return '命令执行出错!' - - def get_help(self): - rsp = '' - for cmd in self.commands: - rsp += '{c}:{d}\n'.format(c=cmd.title, d=cmd.describe) - return rsp - - -if __name__ == '__main__': - chatbot = ChatGPT() - prompt = "写一篇1000字关于AI的论文" - print(chatbot.chat(prompt)) diff --git a/src/DjangoBlog/servermanager/apps.py b/src/DjangoBlog/servermanager/apps.py deleted file mode 100644 index 03cc38d..0000000 --- a/src/DjangoBlog/servermanager/apps.py +++ /dev/null @@ -1,5 +0,0 @@ -from django.apps import AppConfig - - -class ServermanagerConfig(AppConfig): - name = 'servermanager' diff --git a/src/DjangoBlog/servermanager/migrations/0001_initial.py b/src/DjangoBlog/servermanager/migrations/0001_initial.py deleted file mode 100644 index bbdbf77..0000000 --- a/src/DjangoBlog/servermanager/migrations/0001_initial.py +++ /dev/null @@ -1,45 +0,0 @@ -# Generated by Django 4.1.7 on 2023-03-02 07:14 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - initial = True - - dependencies = [ - ] - - operations = [ - migrations.CreateModel( - name='commands', - fields=[ - ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('title', models.CharField(max_length=300, verbose_name='命令标题')), - ('command', models.CharField(max_length=2000, verbose_name='命令')), - ('describe', models.CharField(max_length=300, verbose_name='命令描述')), - ('created_time', models.DateTimeField(auto_now_add=True, verbose_name='创建时间')), - ('last_mod_time', models.DateTimeField(auto_now=True, verbose_name='修改时间')), - ], - options={ - 'verbose_name': '命令', - 'verbose_name_plural': '命令', - }, - ), - migrations.CreateModel( - name='EmailSendLog', - fields=[ - ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('emailto', models.CharField(max_length=300, verbose_name='收件人')), - ('title', models.CharField(max_length=2000, verbose_name='邮件标题')), - ('content', models.TextField(verbose_name='邮件内容')), - ('send_result', models.BooleanField(default=False, verbose_name='结果')), - ('created_time', models.DateTimeField(auto_now_add=True, verbose_name='创建时间')), - ], - options={ - 'verbose_name': '邮件发送log', - 'verbose_name_plural': '邮件发送log', - 'ordering': ['-created_time'], - }, - ), - ] diff --git a/src/DjangoBlog/servermanager/migrations/0002_alter_emailsendlog_options_and_more.py b/src/DjangoBlog/servermanager/migrations/0002_alter_emailsendlog_options_and_more.py deleted file mode 100644 index 4858857..0000000 --- a/src/DjangoBlog/servermanager/migrations/0002_alter_emailsendlog_options_and_more.py +++ /dev/null @@ -1,32 +0,0 @@ -# Generated by Django 4.2.5 on 2023-09-06 13:19 - -from django.db import migrations - - -class Migration(migrations.Migration): - - dependencies = [ - ('servermanager', '0001_initial'), - ] - - operations = [ - migrations.AlterModelOptions( - name='emailsendlog', - options={'ordering': ['-creation_time'], 'verbose_name': '邮件发送log', 'verbose_name_plural': '邮件发送log'}, - ), - migrations.RenameField( - model_name='commands', - old_name='created_time', - new_name='creation_time', - ), - migrations.RenameField( - model_name='commands', - old_name='last_mod_time', - new_name='last_modify_time', - ), - migrations.RenameField( - model_name='emailsendlog', - old_name='created_time', - new_name='creation_time', - ), - ] diff --git a/src/DjangoBlog/servermanager/migrations/__init__.py b/src/DjangoBlog/servermanager/migrations/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/src/DjangoBlog/servermanager/models.py b/src/DjangoBlog/servermanager/models.py deleted file mode 100644 index 4326c65..0000000 --- a/src/DjangoBlog/servermanager/models.py +++ /dev/null @@ -1,33 +0,0 @@ -from django.db import models - - -# Create your models here. -class commands(models.Model): - title = models.CharField('命令标题', max_length=300) - command = models.CharField('命令', max_length=2000) - describe = models.CharField('命令描述', max_length=300) - creation_time = models.DateTimeField('创建时间', auto_now_add=True) - last_modify_time = models.DateTimeField('修改时间', auto_now=True) - - def __str__(self): - return self.title - - class Meta: - verbose_name = '命令' - verbose_name_plural = verbose_name - - -class EmailSendLog(models.Model): - emailto = models.CharField('收件人', max_length=300) - title = models.CharField('邮件标题', max_length=2000) - content = models.TextField('邮件内容') - send_result = models.BooleanField('结果', default=False) - creation_time = models.DateTimeField('创建时间', auto_now_add=True) - - def __str__(self): - return self.title - - class Meta: - verbose_name = '邮件发送log' - verbose_name_plural = verbose_name - ordering = ['-creation_time'] diff --git a/src/DjangoBlog/servermanager/robot.py b/src/DjangoBlog/servermanager/robot.py deleted file mode 100644 index 7b45736..0000000 --- a/src/DjangoBlog/servermanager/robot.py +++ /dev/null @@ -1,187 +0,0 @@ -import logging -import os -import re - -import jsonpickle -from django.conf import settings -from werobot import WeRoBot -from werobot.replies import ArticlesReply, Article -from werobot.session.filestorage import FileStorage - -from djangoblog.utils import get_sha256 -from servermanager.api.blogapi import BlogApi -from servermanager.api.commonapi import ChatGPT, CommandHandler -from .MemcacheStorage import MemcacheStorage - -robot = WeRoBot(token=os.environ.get('DJANGO_WEROBOT_TOKEN') - or 'lylinux', enable_session=True) -memstorage = MemcacheStorage() -if memstorage.is_available: - robot.config['SESSION_STORAGE'] = memstorage -else: - if os.path.exists(os.path.join(settings.BASE_DIR, 'werobot_session')): - os.remove(os.path.join(settings.BASE_DIR, 'werobot_session')) - robot.config['SESSION_STORAGE'] = FileStorage(filename='werobot_session') - -blogapi = BlogApi() -cmd_handler = CommandHandler() -logger = logging.getLogger(__name__) - - -def convert_to_article_reply(articles, message): - reply = ArticlesReply(message=message) - from blog.templatetags.blog_tags import truncatechars_content - for post in articles: - imgs = re.findall(r'(?:http\:|https\:)?\/\/.*\.(?:png|jpg)', post.body) - imgurl = '' - if imgs: - imgurl = imgs[0] - article = Article( - title=post.title, - description=truncatechars_content(post.body), - img=imgurl, - url=post.get_full_url() - ) - reply.add_article(article) - return reply - - -@robot.filter(re.compile(r"^\?.*")) -def search(message, session): - s = message.content - searchstr = str(s).replace('?', '') - result = blogapi.search_articles(searchstr) - if result: - articles = list(map(lambda x: x.object, result)) - reply = convert_to_article_reply(articles, message) - return reply - else: - return '没有找到相关文章。' - - -@robot.filter(re.compile(r'^category\s*$', re.I)) -def category(message, session): - categorys = blogapi.get_category_lists() - content = ','.join(map(lambda x: x.name, categorys)) - return '所有文章分类目录:' + content - - -@robot.filter(re.compile(r'^recent\s*$', re.I)) -def recents(message, session): - articles = blogapi.get_recent_articles() - if articles: - reply = convert_to_article_reply(articles, message) - return reply - else: - return "暂时还没有文章" - - -@robot.filter(re.compile('^help$', re.I)) -def help(message, session): - return '''欢迎关注! - 默认会与图灵机器人聊天~~ - 你可以通过下面这些命令来获得信息 - ?关键字搜索文章. - 如?python. - category获得文章分类目录及文章数. - category-***获得该分类目录文章 - 如category-python - recent获得最新文章 - help获得帮助. - weather:获得天气 - 如weather:西安 - idcard:获得身份证信息 - 如idcard:61048119xxxxxxxxxx - music:音乐搜索 - 如music:阴天快乐 - PS:以上标点符号都不支持中文标点~~ - ''' - - -@robot.filter(re.compile(r'^weather\:.*$', re.I)) -def weather(message, session): - return "建设中..." - - -@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 - self.message = message - self.session = session - self.userid = userid - try: - info = session[userid] - self.userinfo = jsonpickle.decode(info) - except Exception as e: - 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: - passwd = '123' - if passwd.upper() == get_sha256(get_sha256(info)).upper(): - self.userinfo.isPasswordSet = True - self.save_session() - return "验证通过,请输入命令或者要执行的命令代码:输入helpme获得帮助" - else: - if self.userinfo.Count >= 3: - self.userinfo = WxUserInfo() - self.save_session() - return "超过验证次数" - 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) - else: - if info.upper() == 'HELPME': - return cmd_handler.get_help() - self.userinfo.Command = info - self.save_session() - return "确认执行: " + info + " 命令?" - - return ChatGPT.chat(info) - - -class WxUserInfo(): - def __init__(self): - self.isAdmin = False - self.isPasswordSet = False - self.Count = 0 - self.Command = '' diff --git a/src/DjangoBlog/servermanager/tests.py b/src/DjangoBlog/servermanager/tests.py deleted file mode 100644 index 22a6689..0000000 --- a/src/DjangoBlog/servermanager/tests.py +++ /dev/null @@ -1,79 +0,0 @@ -from django.test import Client, RequestFactory, TestCase -from django.utils import timezone -from werobot.messages.messages import TextMessage - -from accounts.models import BlogUser -from blog.models import Category, Article -from servermanager.api.commonapi import ChatGPT -from .models import commands -from .robot import MessageHandler, CommandHandler -from .robot import search, category, recents - - -# Create your tests here. -class ServerManagerTest(TestCase): - def setUp(self): - self.client = Client() - self.factory = RequestFactory() - - def test_chat_gpt(self): - content = ChatGPT.chat("你好") - self.assertIsNotNone(content) - - def test_validate_comment(self): - user = BlogUser.objects.create_superuser( - email="liangliangyy1@gmail.com", - username="liangliangyy1", - password="liangliangyy1") - - self.client.login(username='liangliangyy1', password='liangliangyy1') - - c = Category() - c.name = "categoryccc" - c.save() - - article = Article() - article.title = "nicetitleccc" - article.body = "nicecontentccc" - article.author = user - article.category = c - article.type = 'a' - article.status = 'p' - article.save() - s = TextMessage([]) - s.content = "nice" - rsp = search(s, None) - rsp = category(None, None) - self.assertIsNotNone(rsp) - rsp = recents(None, None) - self.assertTrue(rsp != '暂时还没有文章') - - cmd = commands() - cmd.title = "test" - cmd.command = "ls" - cmd.describe = "test" - cmd.save() - - cmdhandler = CommandHandler() - rsp = cmdhandler.run('test') - self.assertIsNotNone(rsp) - s.source = 'u' - s.content = 'test' - msghandler = MessageHandler(s, {}) - - # msghandler.userinfo.isPasswordSet = True - # msghandler.userinfo.isAdmin = True - msghandler.handler() - s.content = 'y' - msghandler.handler() - s.content = 'idcard:12321233' - msghandler.handler() - s.content = 'weather:上海' - msghandler.handler() - s.content = 'admin' - msghandler.handler() - s.content = '123' - msghandler.handler() - - s.content = 'exit' - msghandler.handler() diff --git a/src/DjangoBlog/servermanager/urls.py b/src/DjangoBlog/servermanager/urls.py deleted file mode 100644 index 8d134d2..0000000 --- a/src/DjangoBlog/servermanager/urls.py +++ /dev/null @@ -1,10 +0,0 @@ -from django.urls import path -from werobot.contrib.django import make_view - -from .robot import robot - -app_name = "servermanager" -urlpatterns = [ - path(r'robot', make_view(robot)), - -] diff --git a/src/DjangoBlog/servermanager/views.py b/src/DjangoBlog/servermanager/views.py deleted file mode 100644 index 60f00ef..0000000 --- a/src/DjangoBlog/servermanager/views.py +++ /dev/null @@ -1 +0,0 @@ -# Create your views here. diff --git a/src/DjangoBlog/templates/account/forget_password.html b/src/DjangoBlog/templates/account/forget_password.html deleted file mode 100644 index 3384531..0000000 --- a/src/DjangoBlog/templates/account/forget_password.html +++ /dev/null @@ -1,30 +0,0 @@ -{% extends 'share_layout/base_account.html' %} -{% load i18n %} -{% load static %} -{% block content %} -
- - - - - -

- Home Page - | - login page -

- -
-{% endblock %} \ No newline at end of file diff --git a/src/DjangoBlog/templates/account/login.html b/src/DjangoBlog/templates/account/login.html deleted file mode 100644 index cff8d33..0000000 --- a/src/DjangoBlog/templates/account/login.html +++ /dev/null @@ -1,46 +0,0 @@ -{% extends 'share_layout/base_account.html' %} -{% load static %} -{% load i18n %} -{% block content %} -
- - - - - -

- - {% trans 'Create Account' %} - - | - Home Page - | - - {% trans 'Forget Password' %} - -

- -
-{% endblock %} \ No newline at end of file diff --git a/src/DjangoBlog/templates/account/registration_form.html b/src/DjangoBlog/templates/account/registration_form.html deleted file mode 100644 index 65e7549..0000000 --- a/src/DjangoBlog/templates/account/registration_form.html +++ /dev/null @@ -1,29 +0,0 @@ -{% extends 'share_layout/base_account.html' %} -{% load static %} -{% block content %} -
- - - - - -

- Sign In -

- -
-{% endblock %} \ No newline at end of file diff --git a/src/DjangoBlog/templates/account/result.html b/src/DjangoBlog/templates/account/result.html deleted file mode 100644 index 23c9094..0000000 --- a/src/DjangoBlog/templates/account/result.html +++ /dev/null @@ -1,27 +0,0 @@ -{% extends 'share_layout/base.html' %} -{% load i18n %} -{% block header %} - {{ title }} -{% endblock %} -{% block content %} -
- -
-{% endblock %} \ No newline at end of file diff --git a/src/DjangoBlog/templates/blog/article_archives.html b/src/DjangoBlog/templates/blog/article_archives.html deleted file mode 100644 index 959319e..0000000 --- a/src/DjangoBlog/templates/blog/article_archives.html +++ /dev/null @@ -1,60 +0,0 @@ -{% extends 'share_layout/base.html' %} -{% load blog_tags %} -{% load cache %} -{% load i18n %} -{% block header %} - - {% trans 'article archive' %} | {{ SITE_DESCRIPTION }} - - - - - - - - - -{% endblock %} -{% block content %} -
-
- -
- -

{% trans 'article archive' %}

-
- -
- - {% regroup article_list by pub_time.year as year_post_group %} -
    - {% for year in year_post_group %} -
  • {{ year.grouper }} {% trans 'year' %} - {% regroup year.list by pub_time.month as month_post_group %} -
      - {% for month in month_post_group %} -
    • {{ month.grouper }} {% trans 'month' %} - -
    • - {% endfor %} -
    -
  • - {% endfor %} -
-
-
-
- -{% endblock %} - - -{% block sidebar %} - {% load_sidebar user 'i' %} -{% endblock %} - - diff --git a/src/DjangoBlog/templates/blog/article_detail.html b/src/DjangoBlog/templates/blog/article_detail.html deleted file mode 100755 index a74a0db..0000000 --- a/src/DjangoBlog/templates/blog/article_detail.html +++ /dev/null @@ -1,52 +0,0 @@ -{% extends 'share_layout/base.html' %} -{% load blog_tags %} - -{% block header %} -{% endblock %} -{% block content %} -
-
- {% load_article_detail article False user %} - - {% if article.type == 'a' %} - - {% endif %} - -
- {% if article.comment_status == "o" and OPEN_SITE_COMMENT %} - - - {% include 'comments/tags/comment_list.html' %} - {% if user.is_authenticated %} - {% include 'comments/tags/post_comment.html' %} - {% else %} -
-

您还没有登录,请您登录后发表评论。 -

- - {% load oauth_tags %} - {% load_oauth_applications request %} - -
- {% endif %} - {% endif %} -
- -{% endblock %} - -{% block sidebar %} - {% load_sidebar user "p" %} -{% endblock %} \ No newline at end of file diff --git a/src/DjangoBlog/templates/blog/article_index.html b/src/DjangoBlog/templates/blog/article_index.html deleted file mode 100644 index 0ee6150..0000000 --- a/src/DjangoBlog/templates/blog/article_index.html +++ /dev/null @@ -1,42 +0,0 @@ -{% extends 'share_layout/base.html' %} -{% load blog_tags %} -{% load cache %} -{% block header %} - {% if tag_name %} - {{ page_type }}:{{ tag_name }} | {{ SITE_DESCRIPTION }} - {% comment %}{% endcomment %} - {% else %} - {{ SITE_NAME }} | {{ SITE_DESCRIPTION }} - {% endif %} - - - - - - - -{% endblock %} -{% block content %} -
-
- {% if page_type and tag_name %} -
- -

{{ page_type }}:{{ tag_name }}

-
- {% endif %} - - {% for article in article_list %} - {% load_article_detail article True user %} - {% endfor %} - {% if is_paginated %} - {% load_pagination_info page_obj page_type tag_name %} - - {% endif %} -
-
- -{% endblock %} -{% block sidebar %} - {% load_sidebar user linktype %} -{% endblock %} \ No newline at end of file diff --git a/src/DjangoBlog/templates/blog/error_page.html b/src/DjangoBlog/templates/blog/error_page.html deleted file mode 100644 index d41cfb6..0000000 --- a/src/DjangoBlog/templates/blog/error_page.html +++ /dev/null @@ -1,45 +0,0 @@ -{% extends 'share_layout/base.html' %} -{% load blog_tags %} -{% load cache %} -{% block header %} - {% if tag_name %} - {% if statuscode == '404' %} - 404 NotFound - {% elif statuscode == '403' %} - Permission Denied - {% elif statuscode == '500' %} - 500 Error - {% else %} - - {% endif %} - {% comment %}{% endcomment %} - {% else %} - {{ SITE_NAME }} | {{ SITE_DESCRIPTION }} - {% endif %} - - - - - - - -{% endblock %} -{% block content %} -
-
- -
-

{{ message }}

-
- -
-
- -{% endblock %} - - -{% block sidebar %} - {% load_sidebar user 'i' %} -{% endblock %} - - diff --git a/src/DjangoBlog/templates/blog/links_list.html b/src/DjangoBlog/templates/blog/links_list.html deleted file mode 100644 index ccecbea..0000000 --- a/src/DjangoBlog/templates/blog/links_list.html +++ /dev/null @@ -1,44 +0,0 @@ -{% extends 'share_layout/base.html' %} -{% load blog_tags %} -{% load cache %} -{% block header %} - - 友情链接 | {{ SITE_DESCRIPTION }} - - - - - - - - - -{% endblock %} -{% block content %} -
-
- -
- -

友情链接

-
- -
- -
-
-
- -{% endblock %} - - -{% block sidebar %} - {% load_sidebar user 'i' %} -{% endblock %} - - diff --git a/src/DjangoBlog/templates/blog/tags/article_info.html b/src/DjangoBlog/templates/blog/tags/article_info.html deleted file mode 100644 index 65b45fa..0000000 --- a/src/DjangoBlog/templates/blog/tags/article_info.html +++ /dev/null @@ -1,79 +0,0 @@ -{% load blog_tags %} -{% load cache %} -{% load i18n %} -
-
- -

- {% if isindex %} - {% if article.article_order > 0 %} - 【{% trans 'pin to top' %}】{{ article.title }} - {% else %} - {{ article.title }} - {% endif %} - - {% else %} - {{ article.title }} - {% endif %} -

- -
- {% if article.type == 'a' %} - {% if not isindex %} - {% cache 36000 breadcrumb article.pk %} - {% load_breadcrumb article %} - {% endcache %} - {% endif %} - {% endif %} -
- -
- {% if isindex %} - {% render_article_content article True %} -

Read more

- {% else %} - - {% if article.show_toc %} - {% get_markdown_toc article.body as toc %} - {% trans 'toc' %}: - {{ toc|safe }} - -
- {% endif %} -
- - {% render_article_content article False %} - -
- {% endif %} - -
- - {% load_article_metas article user %} - -
- - -{% if not isindex %} - {% render_plugin_widgets 'article_bottom' article=article %} -{% endif %} \ No newline at end of file diff --git a/src/DjangoBlog/templates/blog/tags/article_meta_info.html b/src/DjangoBlog/templates/blog/tags/article_meta_info.html deleted file mode 100644 index ec8a0f9..0000000 --- a/src/DjangoBlog/templates/blog/tags/article_meta_info.html +++ /dev/null @@ -1,57 +0,0 @@ -{% load i18n %} -{% load blog_tags %} - - - - - diff --git a/src/DjangoBlog/templates/blog/tags/article_pagination.html b/src/DjangoBlog/templates/blog/tags/article_pagination.html deleted file mode 100644 index 95514ff..0000000 --- a/src/DjangoBlog/templates/blog/tags/article_pagination.html +++ /dev/null @@ -1,17 +0,0 @@ -{% load i18n %} - \ No newline at end of file diff --git a/src/DjangoBlog/templates/blog/tags/article_tag_list.html b/src/DjangoBlog/templates/blog/tags/article_tag_list.html deleted file mode 100644 index c8ba474..0000000 --- a/src/DjangoBlog/templates/blog/tags/article_tag_list.html +++ /dev/null @@ -1,19 +0,0 @@ -{% load i18n %} -{% if article_tags_list %} -
-
- {% trans 'tags' %} -
-
- - {% for url,count,tag,color in article_tags_list %} - - {{ tag.name }} - {{ count }} - - {% endfor %} - -
-
-{% endif %} diff --git a/src/DjangoBlog/templates/blog/tags/breadcrumb.html b/src/DjangoBlog/templates/blog/tags/breadcrumb.html deleted file mode 100644 index 67087d5..0000000 --- a/src/DjangoBlog/templates/blog/tags/breadcrumb.html +++ /dev/null @@ -1,19 +0,0 @@ - - diff --git a/src/DjangoBlog/templates/blog/tags/sidebar.html b/src/DjangoBlog/templates/blog/tags/sidebar.html deleted file mode 100755 index ecb6d20..0000000 --- a/src/DjangoBlog/templates/blog/tags/sidebar.html +++ /dev/null @@ -1,136 +0,0 @@ -{% load blog_tags %} -{% load i18n %} - diff --git a/src/DjangoBlog/templates/comments/tags/comment_item.html b/src/DjangoBlog/templates/comments/tags/comment_item.html deleted file mode 100644 index 0693649..0000000 --- a/src/DjangoBlog/templates/comments/tags/comment_item.html +++ /dev/null @@ -1,37 +0,0 @@ -{% load blog_tags %} -
  • -
    - - - -

    {{ comment_item.body|escape|comment_markdown }}

    - -
    - -
  • \ No newline at end of file diff --git a/src/DjangoBlog/templates/comments/tags/comment_item_tree.html b/src/DjangoBlog/templates/comments/tags/comment_item_tree.html deleted file mode 100644 index a407d76..0000000 --- a/src/DjangoBlog/templates/comments/tags/comment_item_tree.html +++ /dev/null @@ -1,57 +0,0 @@ -{% load blog_tags %} -
  • -
    - - - -

    - {% if comment_item.parent_comment %} -

    - {% endif %} -

    - -

    {{ comment_item.body|escape|comment_markdown }}

    - - -
    - -
  • -{% query article_comments parent_comment=comment_item as cc_comments %} -{% for cc in cc_comments %} - {% with comment_item=cc template_name="comments/tags/comment_item_tree.html" %} - {% if depth >= 1 %} - {% include template_name %} - {% else %} - {% with depth=depth|add:1 %} - {% include template_name %} - {% endwith %} - {% endif %} - {% endwith %} -{% endfor %} \ No newline at end of file diff --git a/src/DjangoBlog/templates/comments/tags/comment_list.html b/src/DjangoBlog/templates/comments/tags/comment_list.html deleted file mode 100644 index 4092161..0000000 --- a/src/DjangoBlog/templates/comments/tags/comment_list.html +++ /dev/null @@ -1,45 +0,0 @@ - -
    - {% load blog_tags %} - {% load comments_tags %} - {% load cache %} - - - {% if article_comments %} -
    -
      - {# {% query article_comments parent_comment=None as parent_comments %}#} - {% for comment_item in p_comments %} - - {% with 0 as depth %} - {% include "comments/tags/comment_item_tree.html" %} - {% endwith %} - {% endfor %} - -
    - -
    -
    - {% endif %} -
    - -
    \ No newline at end of file diff --git a/src/DjangoBlog/templates/comments/tags/post_comment.html b/src/DjangoBlog/templates/comments/tags/post_comment.html deleted file mode 100644 index 3ae5a27..0000000 --- a/src/DjangoBlog/templates/comments/tags/post_comment.html +++ /dev/null @@ -1,33 +0,0 @@ -
    - -
    -

    发表评论 - -

    -
    {% csrf_token %} -

    - {{ form.body.label_tag }} - - {{ form.body }} - {{ form.body.errors }} -

    - {{ form.parent_comment_id }} -
    - {% if COMMENT_NEED_REVIEW %} - 支持markdown,评论经审核后才会显示。 - {% else %} - 支持markdown。 - {% endif %} - - -
    -
    -
    - -
    - - diff --git a/src/DjangoBlog/templates/oauth/bindsuccess.html b/src/DjangoBlog/templates/oauth/bindsuccess.html deleted file mode 100644 index 4bee77c..0000000 --- a/src/DjangoBlog/templates/oauth/bindsuccess.html +++ /dev/null @@ -1,22 +0,0 @@ -{% extends 'share_layout/base.html' %} -{% block header %} - {{ title }} -{% endblock %} -{% block content %} -
    -
    - -
    - -

    {{ content }}

    -
    -
    -
    - - 登录 - | - 回到首页 -
    -
    -
    -{% endblock %} \ No newline at end of file diff --git a/src/DjangoBlog/templates/oauth/oauth_applications.html b/src/DjangoBlog/templates/oauth/oauth_applications.html deleted file mode 100644 index a841ad2..0000000 --- a/src/DjangoBlog/templates/oauth/oauth_applications.html +++ /dev/null @@ -1,13 +0,0 @@ -{% load i18n %} - diff --git a/src/DjangoBlog/templates/oauth/require_email.html b/src/DjangoBlog/templates/oauth/require_email.html deleted file mode 100644 index 3adef12..0000000 --- a/src/DjangoBlog/templates/oauth/require_email.html +++ /dev/null @@ -1,46 +0,0 @@ -{% extends 'share_layout/base_account.html' %} - -{% load static %} -{% block content %} -
    - - - - - -

    - 登录 -

    - -
    -{% endblock %} \ No newline at end of file diff --git a/src/DjangoBlog/templates/owntracks/show_log_dates.html b/src/DjangoBlog/templates/owntracks/show_log_dates.html deleted file mode 100644 index 7dbba21..0000000 --- a/src/DjangoBlog/templates/owntracks/show_log_dates.html +++ /dev/null @@ -1,17 +0,0 @@ - - - - - 记录日期 - - - -
      - {% for date in results %} -
    • - {{ date }} -
    • - {% endfor %} -
    - - \ No newline at end of file diff --git a/src/DjangoBlog/templates/owntracks/show_maps.html b/src/DjangoBlog/templates/owntracks/show_maps.html deleted file mode 100644 index 3aeda36..0000000 --- a/src/DjangoBlog/templates/owntracks/show_maps.html +++ /dev/null @@ -1,135 +0,0 @@ - - - - - - - 运动轨迹 - - - -
    - - - - - - - - \ No newline at end of file diff --git a/src/DjangoBlog/templates/plugins/article_recommendation/__init__.py b/src/DjangoBlog/templates/plugins/article_recommendation/__init__.py deleted file mode 100644 index 7d86a99..0000000 --- a/src/DjangoBlog/templates/plugins/article_recommendation/__init__.py +++ /dev/null @@ -1 +0,0 @@ -# 插件模板目录 diff --git a/src/DjangoBlog/templates/plugins/article_recommendation/bottom_widget.html b/src/DjangoBlog/templates/plugins/article_recommendation/bottom_widget.html deleted file mode 100644 index 829b7b4..0000000 --- a/src/DjangoBlog/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/DjangoBlog/templates/plugins/article_recommendation/sidebar_widget.html b/src/DjangoBlog/templates/plugins/article_recommendation/sidebar_widget.html deleted file mode 100644 index 5f1afbf..0000000 --- a/src/DjangoBlog/templates/plugins/article_recommendation/sidebar_widget.html +++ /dev/null @@ -1,17 +0,0 @@ -{% load i18n %} - diff --git a/src/DjangoBlog/templates/plugins/css_includes.html b/src/DjangoBlog/templates/plugins/css_includes.html deleted file mode 100644 index 37029ae..0000000 --- a/src/DjangoBlog/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/DjangoBlog/templates/plugins/js_includes.html b/src/DjangoBlog/templates/plugins/js_includes.html deleted file mode 100644 index 2a315e3..0000000 --- a/src/DjangoBlog/templates/plugins/js_includes.html +++ /dev/null @@ -1,4 +0,0 @@ -{% comment %}插件JS文件包含模板 - 用于压缩{% endcomment %} -{% for js_file in js_files %} - -{% endfor %} diff --git a/src/DjangoBlog/templates/search/indexes/blog/article_text.txt b/src/DjangoBlog/templates/search/indexes/blog/article_text.txt deleted file mode 100644 index 4f9ca76..0000000 --- a/src/DjangoBlog/templates/search/indexes/blog/article_text.txt +++ /dev/null @@ -1,3 +0,0 @@ -{{ object.title }} -{{ object.author.username }} -{{ object.body }} \ No newline at end of file diff --git a/src/DjangoBlog/templates/search/search.html b/src/DjangoBlog/templates/search/search.html deleted file mode 100644 index 1404c60..0000000 --- a/src/DjangoBlog/templates/search/search.html +++ /dev/null @@ -1,66 +0,0 @@ -{% extends 'share_layout/base.html' %} -{% load blog_tags %} -{% block header %} - {{ SITE_NAME }} | {{ SITE_DESCRIPTION }} - - - - - - - -{% endblock %} -{% block content %} -
    -
    - {% if query %} -
    - {% if suggestion %} -

    - 已显示 “{{ suggestion }}” 的搜索结果。   - 仍然搜索:{{ query }}
    -

    - {% else %} -

    - 搜索:{{ query }}    -

    - {% endif %} -
    - {% endif %} - {% if query and page.object_list %} - {% for article in page.object_list %} - {% load_article_detail article.object True user %} - {% endfor %} - {% if page.has_previous or page.has_next %} - - - {% endif %} - {% else %} -
    - -

    哎呀,关键字:{{ query }}没有找到结果,要不换个词再试试?

    -
    - {% endif %} -
    -
    -{% endblock %} - - -{% block sidebar %} - {% load_sidebar request.user 'i' %} -{% endblock %} - - diff --git a/src/DjangoBlog/templates/share_layout/adsense.html b/src/DjangoBlog/templates/share_layout/adsense.html deleted file mode 100644 index 8f99c55..0000000 --- a/src/DjangoBlog/templates/share_layout/adsense.html +++ /dev/null @@ -1,6 +0,0 @@ - \ No newline at end of file diff --git a/src/DjangoBlog/templates/share_layout/base.html b/src/DjangoBlog/templates/share_layout/base.html deleted file mode 100644 index bb17933..0000000 --- a/src/DjangoBlog/templates/share_layout/base.html +++ /dev/null @@ -1,119 +0,0 @@ -{% load static %} -{% load cache %} -{% load i18n %} -{% load compress %} - - - - - - - - - - - - {% load blog_tags %} - {% head_meta %} - {% block header %} - - {% endblock %} - - - - - - - - - - - - - - - - - - - {% compress css %} - - - {% comment %}{% endcomment %} - - - - {% block compress_css %} - {% endblock %} - - {% plugin_compressed_css %} - {% endcompress %} - - {% if GLOBAL_HEADER %} - {{ GLOBAL_HEADER|safe }} - {% endif %} - - - {% plugin_head_resources %} - - - -
    - -
    - - {% block content %} - {% endblock %} - - - {% block sidebar %} - {% endblock %} - - -
    - {% include 'share_layout/footer.html' %} -
    - - -{% compress js %} - - - - - {% block compress_js %} - {% endblock %} - - {% plugin_compressed_js %} -{% endcompress %} - - - - -{% block footer %} -{% endblock %} - - -{% plugin_body_resources %} - - diff --git a/src/DjangoBlog/templates/share_layout/base_account.html b/src/DjangoBlog/templates/share_layout/base_account.html deleted file mode 100644 index c00d842..0000000 --- a/src/DjangoBlog/templates/share_layout/base_account.html +++ /dev/null @@ -1,47 +0,0 @@ - - - - {% load static %} - - - - - - - - - {{ SITE_NAME }} | {{ SITE_DESCRIPTION }} - - {% load compress %} - {% compress css %} - - - - - - - - - - {% endcompress %} - {% compress js %} - - - {% endcompress %} - - - - - -{% block content %} -{% endblock %} - - - - - - - \ No newline at end of file diff --git a/src/DjangoBlog/templates/share_layout/footer.html b/src/DjangoBlog/templates/share_layout/footer.html deleted file mode 100644 index cd86a29..0000000 --- a/src/DjangoBlog/templates/share_layout/footer.html +++ /dev/null @@ -1,56 +0,0 @@ - - - diff --git a/src/DjangoBlog/templates/share_layout/nav.html b/src/DjangoBlog/templates/share_layout/nav.html deleted file mode 100644 index 24d4da6..0000000 --- a/src/DjangoBlog/templates/share_layout/nav.html +++ /dev/null @@ -1,30 +0,0 @@ -{% load i18n %} - - \ No newline at end of file diff --git a/src/DjangoBlog/templates/share_layout/nav_node.html b/src/DjangoBlog/templates/share_layout/nav_node.html deleted file mode 100644 index c266880..0000000 --- a/src/DjangoBlog/templates/share_layout/nav_node.html +++ /dev/null @@ -1,19 +0,0 @@ - - -