├── .gitignore ├── Hello_World └── mysite │ ├── article │ ├── __init__.py │ ├── admin.py │ ├── apps.py │ ├── migrations │ │ ├── 0001_initial.py │ │ ├── 0002_article_created_time.py │ │ ├── 0003_auto_20180819_1105.py │ │ ├── 0004_auto_20180819_1106.py │ │ ├── 0005_auto_20180819_1107.py │ │ ├── 0006_article_last_updated_time.py │ │ ├── 0007_remove_article_created_time.py │ │ ├── 0008_article_created_time.py │ │ ├── 0009_article_auther.py │ │ ├── 0010_auto_20180819_1115.py │ │ ├── 0011_auto_20180819_1116.py │ │ └── __init__.py │ ├── models.py │ ├── templates │ │ ├── article_detail.html │ │ └── article_list.html │ ├── tests.py │ ├── urls.py │ └── views.py │ ├── manage.py │ └── mysite │ ├── __init__.py │ ├── settings.py │ ├── urls.py │ ├── views.py │ └── wsgi.py ├── README.md ├── images ├── web.png ├── 回复评论样式.png ├── 富文本表单.png └── 评论回复.png └── mysite ├── blog ├── __init__.py ├── admin.py ├── apps.py ├── migrations │ ├── 0001_initial.py │ ├── 0002_auto_20180821_1438.py │ ├── 0003_auto_20180821_1841.py │ ├── 0004_auto_20180822_1030.py │ ├── 0005_auto_20180822_1054.py │ ├── 0006_blog_readed_num.py │ ├── 0007_auto_20180822_1331.py │ ├── 0008_auto_20180822_1510.py │ ├── 0009_auto_20180825_1036.py │ └── __init__.py ├── models.py ├── static │ └── blog │ │ └── blog.css ├── templates │ └── blog │ │ ├── blog_detail.html │ │ ├── blog_list.html │ │ ├── blogs_with_date.html │ │ └── blogs_with_type.html ├── tests.py ├── urls.py └── views.py ├── comment ├── __init__.py ├── admin.py ├── apps.py ├── forms.py ├── migrations │ ├── 0001_initial.py │ ├── 0002_auto_20180824_1532.py │ ├── 0003_auto_20180824_1539.py │ ├── 0004_auto_20180824_1544.py │ ├── 0005_auto_20180824_1719.py │ ├── 0006_auto_20180825_1036.py │ └── __init__.py ├── models.py ├── templates │ └── comment │ │ └── send_mail.html ├── templatetags │ ├── __init__.py │ └── comment_tags.py ├── tests.py ├── urls.py └── views.py ├── data.json ├── db.sqlite3 ├── likes ├── __init__.py ├── admin.py ├── apps.py ├── migrations │ ├── 0001_initial.py │ ├── 0002_auto_20180825_1238.py │ └── __init__.py ├── models.py ├── templatetags │ ├── __init__.py │ └── likes_tags.py ├── tests.py ├── urls.py └── views.py ├── manage.py ├── media └── upload │ └── 2018 │ └── 08 │ └── 22 │ └── wechatimg95.jpeg ├── mysite ├── __init__.py ├── settings.py ├── urls.py ├── views.py └── wsgi.py ├── read_statistics ├── __init__.py ├── admin.py ├── apps.py ├── migrations │ ├── 0001_initial.py │ ├── 0002_readdetail.py │ ├── 0003_auto_20180825_1036.py │ └── __init__.py ├── models.py ├── tests.py ├── utils.py └── views.py ├── static ├── base.css ├── bootstrap-3.3.7 │ ├── css │ │ ├── bootstrap.min.css │ │ └── bootstrap.min.css.map │ ├── fonts │ │ ├── glyphicons-halflings-regular.eot │ │ ├── glyphicons-halflings-regular.svg │ │ ├── glyphicons-halflings-regular.ttf │ │ ├── glyphicons-halflings-regular.woff │ │ └── glyphicons-halflings-regular.woff2 │ └── js │ │ └── bootstrap.min.js ├── home.css └── jquery-1.12.4.min.js ├── templates ├── base.html ├── error.html ├── form.html └── home.html └── user ├── __init__.py ├── admin.py ├── context_processors.py ├── forms.py ├── migrations ├── 0001_initial.py └── __init__.py ├── models.py ├── templates └── user │ ├── bind_email.html │ ├── forgot_password.html │ ├── login.html │ ├── register.html │ └── user_info.html ├── urls.py └── views.py /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheels/ 23 | *.egg-info/ 24 | .installed.cfg 25 | *.egg 26 | MANIFEST 27 | 28 | # PyInstaller 29 | # Usually these files are written by a python script from a template 30 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 31 | *.manifest 32 | *.spec 33 | 34 | # Installer logs 35 | pip-log.txt 36 | pip-delete-this-directory.txt 37 | 38 | # Unit test / coverage reports 39 | htmlcov/ 40 | .tox/ 41 | .coverage 42 | .coverage.* 43 | .cache 44 | nosetests.xml 45 | coverage.xml 46 | *.cover 47 | .hypothesis/ 48 | .pytest_cache/ 49 | 50 | # Translations 51 | *.mo 52 | *.pot 53 | 54 | # Django stuff: 55 | *.log 56 | local_settings.py 57 | #db.sqlite3 58 | 59 | # Flask stuff: 60 | instance/ 61 | .webassets-cache 62 | 63 | # Scrapy stuff: 64 | .scrapy 65 | 66 | # Sphinx documentation 67 | docs/_build/ 68 | 69 | # PyBuilder 70 | target/ 71 | 72 | # Jupyter Notebook 73 | .ipynb_checkpoints 74 | 75 | # pyenv 76 | .python-version 77 | 78 | # celery beat schedule file 79 | celerybeat-schedule 80 | 81 | # SageMath parsed files 82 | *.sage.py 83 | 84 | # Environments 85 | .env 86 | .venv 87 | env/ 88 | venv/ 89 | ENV/ 90 | env.bak/ 91 | venv.bak/ 92 | 93 | # Spyder project settings 94 | .spyderproject 95 | .spyproject 96 | 97 | # Rope project settings 98 | .ropeproject 99 | 100 | # mkdocs documentation 101 | /site 102 | 103 | # mypy 104 | .mypy_cache/ 105 | -------------------------------------------------------------------------------- /Hello_World/mysite/article/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/able8/Django-Course/8cc6cabe7789117f37e009db842b8f8e41d8e5a5/Hello_World/mysite/article/__init__.py -------------------------------------------------------------------------------- /Hello_World/mysite/article/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | from .models import Article 3 | # Register your models here. 4 | @admin.register(Article) 5 | class ArticleAdmin(admin.ModelAdmin): 6 | list_display = ('id', 'title', 'author','is_deleted', 'created_time', 'last_updated_time', 'content') 7 | # ordering = ('-id', ) 倒序 8 | ordering = ('id', ) 9 | 10 | #admin.site.register(Article, ArticleAdmin) 11 | -------------------------------------------------------------------------------- /Hello_World/mysite/article/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | 3 | 4 | class ArticleConfig(AppConfig): 5 | name = 'article' 6 | -------------------------------------------------------------------------------- /Hello_World/mysite/article/migrations/0001_initial.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.0.5 on 2018-08-19 00:57 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | initial = True 9 | 10 | dependencies = [ 11 | ] 12 | 13 | operations = [ 14 | migrations.CreateModel( 15 | name='Article', 16 | fields=[ 17 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 18 | ('title', models.CharField(max_length=30)), 19 | ('content', models.TextField()), 20 | ], 21 | ), 22 | ] 23 | -------------------------------------------------------------------------------- /Hello_World/mysite/article/migrations/0002_article_created_time.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.0.5 on 2018-08-19 03:03 2 | 3 | from django.db import migrations, models 4 | import django.utils.timezone 5 | 6 | 7 | class Migration(migrations.Migration): 8 | 9 | dependencies = [ 10 | ('article', '0001_initial'), 11 | ] 12 | 13 | operations = [ 14 | migrations.AddField( 15 | model_name='article', 16 | name='created_time', 17 | field=models.DateField(default=django.utils.timezone.now), 18 | preserve_default=False, 19 | ), 20 | ] 21 | -------------------------------------------------------------------------------- /Hello_World/mysite/article/migrations/0003_auto_20180819_1105.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.0.5 on 2018-08-19 03:05 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('article', '0002_article_created_time'), 10 | ] 11 | 12 | operations = [ 13 | migrations.AlterField( 14 | model_name='article', 15 | name='created_time', 16 | field=models.DateTimeField(), 17 | ), 18 | ] 19 | -------------------------------------------------------------------------------- /Hello_World/mysite/article/migrations/0004_auto_20180819_1106.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.0.5 on 2018-08-19 03:06 2 | 3 | from django.db import migrations, models 4 | import django.utils.timezone 5 | 6 | 7 | class Migration(migrations.Migration): 8 | 9 | dependencies = [ 10 | ('article', '0003_auto_20180819_1105'), 11 | ] 12 | 13 | operations = [ 14 | migrations.AlterField( 15 | model_name='article', 16 | name='created_time', 17 | field=models.DateTimeField(default=django.utils.timezone.now), 18 | ), 19 | ] 20 | -------------------------------------------------------------------------------- /Hello_World/mysite/article/migrations/0005_auto_20180819_1107.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.0.5 on 2018-08-19 03:07 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('article', '0004_auto_20180819_1106'), 10 | ] 11 | 12 | operations = [ 13 | migrations.AlterField( 14 | model_name='article', 15 | name='created_time', 16 | field=models.DateTimeField(auto_now_add=True), 17 | ), 18 | ] 19 | -------------------------------------------------------------------------------- /Hello_World/mysite/article/migrations/0006_article_last_updated_time.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.0.5 on 2018-08-19 03:09 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('article', '0005_auto_20180819_1107'), 10 | ] 11 | 12 | operations = [ 13 | migrations.AddField( 14 | model_name='article', 15 | name='last_updated_time', 16 | field=models.DateTimeField(auto_now=True), 17 | ), 18 | ] 19 | -------------------------------------------------------------------------------- /Hello_World/mysite/article/migrations/0007_remove_article_created_time.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.0.5 on 2018-08-19 03:10 2 | 3 | from django.db import migrations 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('article', '0006_article_last_updated_time'), 10 | ] 11 | 12 | operations = [ 13 | migrations.RemoveField( 14 | model_name='article', 15 | name='created_time', 16 | ), 17 | ] 18 | -------------------------------------------------------------------------------- /Hello_World/mysite/article/migrations/0008_article_created_time.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.0.5 on 2018-08-19 03:10 2 | 3 | from django.db import migrations, models 4 | import django.utils.timezone 5 | 6 | 7 | class Migration(migrations.Migration): 8 | 9 | dependencies = [ 10 | ('article', '0007_remove_article_created_time'), 11 | ] 12 | 13 | operations = [ 14 | migrations.AddField( 15 | model_name='article', 16 | name='created_time', 17 | field=models.DateTimeField(auto_now_add=True, default=django.utils.timezone.now), 18 | preserve_default=False, 19 | ), 20 | ] 21 | -------------------------------------------------------------------------------- /Hello_World/mysite/article/migrations/0009_article_auther.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.0.5 on 2018-08-19 03:14 2 | 3 | from django.conf import settings 4 | from django.db import migrations, models 5 | import django.db.models.deletion 6 | 7 | 8 | class Migration(migrations.Migration): 9 | 10 | dependencies = [ 11 | migrations.swappable_dependency(settings.AUTH_USER_MODEL), 12 | ('article', '0008_article_created_time'), 13 | ] 14 | 15 | operations = [ 16 | migrations.AddField( 17 | model_name='article', 18 | name='auther', 19 | field=models.ForeignKey(default=1, on_delete=django.db.models.deletion.DO_NOTHING, to=settings.AUTH_USER_MODEL), 20 | ), 21 | ] 22 | -------------------------------------------------------------------------------- /Hello_World/mysite/article/migrations/0010_auto_20180819_1115.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.0.5 on 2018-08-19 03:15 2 | 3 | from django.db import migrations 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('article', '0009_article_auther'), 10 | ] 11 | 12 | operations = [ 13 | migrations.RenameField( 14 | model_name='article', 15 | old_name='auther', 16 | new_name='author', 17 | ), 18 | ] 19 | -------------------------------------------------------------------------------- /Hello_World/mysite/article/migrations/0011_auto_20180819_1116.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.0.5 on 2018-08-19 03:16 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('article', '0010_auto_20180819_1115'), 10 | ] 11 | 12 | operations = [ 13 | migrations.AddField( 14 | model_name='article', 15 | name='is_deleted', 16 | field=models.BooleanField(default=False), 17 | ), 18 | migrations.AddField( 19 | model_name='article', 20 | name='readed_num', 21 | field=models.IntegerField(default=0), 22 | ), 23 | ] 24 | -------------------------------------------------------------------------------- /Hello_World/mysite/article/migrations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/able8/Django-Course/8cc6cabe7789117f37e009db842b8f8e41d8e5a5/Hello_World/mysite/article/migrations/__init__.py -------------------------------------------------------------------------------- /Hello_World/mysite/article/models.py: -------------------------------------------------------------------------------- 1 | from django.db import models 2 | from django.utils import timezone 3 | from django.contrib.auth.models import User 4 | 5 | # Create your models here. 6 | class Article(models.Model): 7 | title = models.CharField(max_length=30) 8 | content = models.TextField() 9 | # created_time = models.DateTimeField(default=timezone.now) 10 | created_time = models.DateTimeField(auto_now_add=True) 11 | last_updated_time = models.DateTimeField(auto_now=True) 12 | author = models.ForeignKey(User, on_delete=models.CASCADE, default=1) 13 | is_deleted = models.BooleanField(default=False) 14 | readed_num = models.IntegerField(default=0) 15 | 16 | 17 | def __str__(self): 18 | return '' % self.title -------------------------------------------------------------------------------- /Hello_World/mysite/article/templates/article_detail.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 标题 8 | 9 | 10 |

{{ article_obj.title }}

11 |
12 |

{{ article_obj.content }}

13 | 14 | -------------------------------------------------------------------------------- /Hello_World/mysite/article/templates/article_list.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 文章列表 8 | 9 | 10 | {% for article in articles %} 11 | 12 |

{{ article.title }}

13 | {% endfor %} 14 | 15 | ', views.article_detail, name='article_detail'), 9 | ] -------------------------------------------------------------------------------- /Hello_World/mysite/article/views.py: -------------------------------------------------------------------------------- 1 | from django.shortcuts import render, get_object_or_404 2 | from django.http import HttpResponse, Http404 3 | from .models import Article 4 | 5 | # Create your views here. 6 | def article_detail(request, article_id): 7 | # article = Article.objects.get(id=article_id) 8 | article = get_object_or_404(Article, pk=article_id) 9 | context = {} 10 | context['article_obj'] = article 11 | # return render(request, 'article_detail.html', context) 12 | return render(request, 'article_detail.html', context) 13 | 14 | def article_list(request): 15 | # articles = Article.objects.all() 16 | articles = Article.objects.filter(is_deleted=False) 17 | context = {} 18 | context['articles'] = articles 19 | return render(request, 'article_list.html', context) -------------------------------------------------------------------------------- /Hello_World/mysite/manage.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | import os 3 | import sys 4 | 5 | if __name__ == "__main__": 6 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "mysite.settings") 7 | try: 8 | from django.core.management import execute_from_command_line 9 | except ImportError as exc: 10 | raise ImportError( 11 | "Couldn't import Django. Are you sure it's installed and " 12 | "available on your PYTHONPATH environment variable? Did you " 13 | "forget to activate a virtual environment?" 14 | ) from exc 15 | execute_from_command_line(sys.argv) 16 | -------------------------------------------------------------------------------- /Hello_World/mysite/mysite/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/able8/Django-Course/8cc6cabe7789117f37e009db842b8f8e41d8e5a5/Hello_World/mysite/mysite/__init__.py -------------------------------------------------------------------------------- /Hello_World/mysite/mysite/settings.py: -------------------------------------------------------------------------------- 1 | """ 2 | Django settings for mysite project. 3 | 4 | Generated by 'django-admin startproject' using Django 2.0.5. 5 | 6 | For more information on this file, see 7 | https://docs.djangoproject.com/en/2.0/topics/settings/ 8 | 9 | For the full list of settings and their values, see 10 | https://docs.djangoproject.com/en/2.0/ref/settings/ 11 | """ 12 | 13 | import os 14 | 15 | # Build paths inside the project like this: os.path.join(BASE_DIR, ...) 16 | BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) 17 | 18 | 19 | # Quick-start development settings - unsuitable for production 20 | # See https://docs.djangoproject.com/en/2.0/howto/deployment/checklist/ 21 | 22 | # SECURITY WARNING: keep the secret key used in production secret! 23 | SECRET_KEY = '0ik+8#_8po&--dq5)80n8!cyh#mm6%l6$scsoh@9+^io+$&c2c' 24 | 25 | # SECURITY WARNING: don't run with debug turned on in production! 26 | DEBUG = True 27 | 28 | ALLOWED_HOSTS = [] 29 | 30 | 31 | # Application definition 32 | 33 | INSTALLED_APPS = [ 34 | 'django.contrib.admin', 35 | 'django.contrib.auth', 36 | 'django.contrib.contenttypes', 37 | 'django.contrib.sessions', 38 | 'django.contrib.messages', 39 | 'django.contrib.staticfiles', 40 | 'article', 41 | ] 42 | 43 | MIDDLEWARE = [ 44 | 'django.middleware.security.SecurityMiddleware', 45 | 'django.contrib.sessions.middleware.SessionMiddleware', 46 | 'django.middleware.common.CommonMiddleware', 47 | 'django.middleware.csrf.CsrfViewMiddleware', 48 | 'django.contrib.auth.middleware.AuthenticationMiddleware', 49 | 'django.contrib.messages.middleware.MessageMiddleware', 50 | 'django.middleware.clickjacking.XFrameOptionsMiddleware', 51 | ] 52 | 53 | ROOT_URLCONF = 'mysite.urls' 54 | 55 | TEMPLATES = [ 56 | { 57 | 'BACKEND': 'django.template.backends.django.DjangoTemplates', 58 | 'DIRS': [], 59 | 'APP_DIRS': True, 60 | 'OPTIONS': { 61 | 'context_processors': [ 62 | 'django.template.context_processors.debug', 63 | 'django.template.context_processors.request', 64 | 'django.contrib.auth.context_processors.auth', 65 | 'django.contrib.messages.context_processors.messages', 66 | ], 67 | }, 68 | }, 69 | ] 70 | 71 | WSGI_APPLICATION = 'mysite.wsgi.application' 72 | 73 | 74 | # Database 75 | # https://docs.djangoproject.com/en/2.0/ref/settings/#databases 76 | 77 | DATABASES = { 78 | 'default': { 79 | 'ENGINE': 'django.db.backends.sqlite3', 80 | 'NAME': os.path.join(BASE_DIR, 'db.sqlite3'), 81 | } 82 | } 83 | 84 | 85 | # Password validation 86 | # https://docs.djangoproject.com/en/2.0/ref/settings/#auth-password-validators 87 | 88 | AUTH_PASSWORD_VALIDATORS = [ 89 | { 90 | 'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator', 91 | }, 92 | { 93 | 'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator', 94 | }, 95 | { 96 | 'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator', 97 | }, 98 | { 99 | 'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator', 100 | }, 101 | ] 102 | 103 | 104 | # Internationalization 105 | # https://docs.djangoproject.com/en/2.0/topics/i18n/ 106 | 107 | # LANGUAGE_CODE = 'en-us' 108 | LANGUAGE_CODE = 'zh-Hans' 109 | 110 | # TIME_ZONE = 'UTC' 111 | TIME_ZONE = 'Asia/Shanghai' 112 | 113 | USE_I18N = True 114 | 115 | USE_L10N = True 116 | 117 | USE_TZ = True 118 | 119 | 120 | # Static files (CSS, JavaScript, Images) 121 | # https://docs.djangoproject.com/en/2.0/howto/static-files/ 122 | 123 | STATIC_URL = '/static/' 124 | -------------------------------------------------------------------------------- /Hello_World/mysite/mysite/urls.py: -------------------------------------------------------------------------------- 1 | """mysite URL Configuration 2 | 3 | The `urlpatterns` list routes URLs to views. For more information please see: 4 | https://docs.djangoproject.com/en/2.0/topics/http/urls/ 5 | Examples: 6 | Function views 7 | 1. Add an import: from my_app import views 8 | 2. Add a URL to urlpatterns: path('', views.home, name='home') 9 | Class-based views 10 | 1. Add an import: from other_app.views import Home 11 | 2. Add a URL to urlpatterns: path('', Home.as_view(), name='home') 12 | Including another URLconf 13 | 1. Import the include() function: from django.urls import include, path 14 | 2. Add a URL to urlpatterns: path('blog/', include('blog.urls')) 15 | """ 16 | from django.contrib import admin 17 | from django.urls import path, include 18 | from . import views 19 | 20 | urlpatterns = [ 21 | path('admin/', admin.site.urls), 22 | path('', views.index), 23 | path('article/', include('article.urls')) 24 | ] 25 | -------------------------------------------------------------------------------- /Hello_World/mysite/mysite/views.py: -------------------------------------------------------------------------------- 1 | from django.http import HttpResponse 2 | 3 | def index(request): 4 | return HttpResponse('Hello, World!') -------------------------------------------------------------------------------- /Hello_World/mysite/mysite/wsgi.py: -------------------------------------------------------------------------------- 1 | """ 2 | WSGI config for mysite project. 3 | 4 | It exposes the WSGI callable as a module-level variable named ``application``. 5 | 6 | For more information on this file, see 7 | https://docs.djangoproject.com/en/2.0/howto/deployment/wsgi/ 8 | """ 9 | 10 | import os 11 | 12 | from django.core.wsgi import get_wsgi_application 13 | 14 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "mysite.settings") 15 | 16 | application = get_wsgi_application() 17 | -------------------------------------------------------------------------------- /images/web.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/able8/Django-Course/8cc6cabe7789117f37e009db842b8f8e41d8e5a5/images/web.png -------------------------------------------------------------------------------- /images/回复评论样式.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/able8/Django-Course/8cc6cabe7789117f37e009db842b8f8e41d8e5a5/images/回复评论样式.png -------------------------------------------------------------------------------- /images/富文本表单.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/able8/Django-Course/8cc6cabe7789117f37e009db842b8f8e41d8e5a5/images/富文本表单.png -------------------------------------------------------------------------------- /images/评论回复.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/able8/Django-Course/8cc6cabe7789117f37e009db842b8f8e41d8e5a5/images/评论回复.png -------------------------------------------------------------------------------- /mysite/blog/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/able8/Django-Course/8cc6cabe7789117f37e009db842b8f8e41d8e5a5/mysite/blog/__init__.py -------------------------------------------------------------------------------- /mysite/blog/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | from .models import BlogType, Blog 3 | # Register your models here. 4 | @admin.register(BlogType) 5 | class BlogTypeAdmin(admin.ModelAdmin): 6 | list_display = ('id', 'type_name') 7 | 8 | @admin.register(Blog) 9 | class BlogAdmin(admin.ModelAdmin): 10 | list_display = ('id', 'title', 'blog_type','get_read_num', 'author', 'created_time', 'last_updated_time') 11 | ordering = ('id',) 12 | 13 | ''' 14 | @admin.register(ReadNum) 15 | class ReadNumAdmin(admin.ModelAdmin): 16 | list_display = ('read_num', 'blog') 17 | ''' -------------------------------------------------------------------------------- /mysite/blog/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | 3 | 4 | class BlogConfig(AppConfig): 5 | name = 'blog' 6 | -------------------------------------------------------------------------------- /mysite/blog/migrations/0001_initial.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.0.5 on 2018-08-19 04:19 2 | 3 | from django.conf import settings 4 | from django.db import migrations, models 5 | import django.db.models.deletion 6 | 7 | 8 | class Migration(migrations.Migration): 9 | 10 | initial = True 11 | 12 | dependencies = [ 13 | migrations.swappable_dependency(settings.AUTH_USER_MODEL), 14 | ] 15 | 16 | operations = [ 17 | migrations.CreateModel( 18 | name='Blog', 19 | fields=[ 20 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 21 | ('title', models.CharField(max_length=50)), 22 | ('content', models.TextField()), 23 | ('created_time', models.DateTimeField(auto_now_add=True)), 24 | ('last_updated_time', models.DateTimeField(auto_now=True)), 25 | ('author', models.ForeignKey(on_delete=django.db.models.deletion.DO_NOTHING, to=settings.AUTH_USER_MODEL)), 26 | ], 27 | ), 28 | migrations.CreateModel( 29 | name='BlogType', 30 | fields=[ 31 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 32 | ('type_name', models.CharField(max_length=15)), 33 | ], 34 | ), 35 | migrations.AddField( 36 | model_name='blog', 37 | name='blog_type', 38 | field=models.ForeignKey(on_delete=django.db.models.deletion.DO_NOTHING, to='blog.BlogType'), 39 | ), 40 | ] 41 | -------------------------------------------------------------------------------- /mysite/blog/migrations/0002_auto_20180821_1438.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.0.5 on 2018-08-21 06:38 2 | 3 | from django.db import migrations 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('blog', '0001_initial'), 10 | ] 11 | 12 | operations = [ 13 | migrations.AlterModelOptions( 14 | name='blog', 15 | options={'ordering': ['-created_time']}, 16 | ), 17 | ] 18 | -------------------------------------------------------------------------------- /mysite/blog/migrations/0003_auto_20180821_1841.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.0.5 on 2018-08-21 10:41 2 | 3 | from django.db import migrations 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('blog', '0002_auto_20180821_1438'), 10 | ] 11 | 12 | operations = [ 13 | migrations.AlterModelOptions( 14 | name='blog', 15 | options={'ordering': ['created_time']}, 16 | ), 17 | ] 18 | -------------------------------------------------------------------------------- /mysite/blog/migrations/0004_auto_20180822_1030.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.0.5 on 2018-08-22 02:30 2 | 3 | import ckeditor.fields 4 | from django.db import migrations 5 | 6 | 7 | class Migration(migrations.Migration): 8 | 9 | dependencies = [ 10 | ('blog', '0003_auto_20180821_1841'), 11 | ] 12 | 13 | operations = [ 14 | migrations.AlterField( 15 | model_name='blog', 16 | name='content', 17 | field=ckeditor.fields.RichTextField(), 18 | ), 19 | ] 20 | -------------------------------------------------------------------------------- /mysite/blog/migrations/0005_auto_20180822_1054.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.0.5 on 2018-08-22 02:54 2 | 3 | import ckeditor_uploader.fields 4 | from django.db import migrations 5 | 6 | 7 | class Migration(migrations.Migration): 8 | 9 | dependencies = [ 10 | ('blog', '0004_auto_20180822_1030'), 11 | ] 12 | 13 | operations = [ 14 | migrations.AlterField( 15 | model_name='blog', 16 | name='content', 17 | field=ckeditor_uploader.fields.RichTextUploadingField(), 18 | ), 19 | ] 20 | -------------------------------------------------------------------------------- /mysite/blog/migrations/0006_blog_readed_num.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.0.5 on 2018-08-22 03:13 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('blog', '0005_auto_20180822_1054'), 10 | ] 11 | 12 | operations = [ 13 | migrations.AddField( 14 | model_name='blog', 15 | name='readed_num', 16 | field=models.IntegerField(default=0), 17 | ), 18 | ] 19 | -------------------------------------------------------------------------------- /mysite/blog/migrations/0007_auto_20180822_1331.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.0.5 on 2018-08-22 05:31 2 | 3 | from django.db import migrations, models 4 | import django.db.models.deletion 5 | 6 | 7 | class Migration(migrations.Migration): 8 | 9 | dependencies = [ 10 | ('blog', '0006_blog_readed_num'), 11 | ] 12 | 13 | operations = [ 14 | migrations.CreateModel( 15 | name='ReadNum', 16 | fields=[ 17 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 18 | ('read_num', models.IntegerField(default=0)), 19 | ], 20 | ), 21 | migrations.RemoveField( 22 | model_name='blog', 23 | name='readed_num', 24 | ), 25 | migrations.AddField( 26 | model_name='readnum', 27 | name='blog', 28 | field=models.OneToOneField(on_delete=django.db.models.deletion.DO_NOTHING, to='blog.Blog'), 29 | ), 30 | ] 31 | -------------------------------------------------------------------------------- /mysite/blog/migrations/0008_auto_20180822_1510.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.0.5 on 2018-08-22 07:10 2 | 3 | from django.db import migrations 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('blog', '0007_auto_20180822_1331'), 10 | ] 11 | 12 | operations = [ 13 | migrations.RemoveField( 14 | model_name='readnum', 15 | name='blog', 16 | ), 17 | migrations.DeleteModel( 18 | name='ReadNum', 19 | ), 20 | ] 21 | -------------------------------------------------------------------------------- /mysite/blog/migrations/0009_auto_20180825_1036.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.0.5 on 2018-08-25 02:36 2 | 3 | from django.conf import settings 4 | from django.db import migrations, models 5 | import django.db.models.deletion 6 | 7 | 8 | class Migration(migrations.Migration): 9 | 10 | dependencies = [ 11 | ('blog', '0008_auto_20180822_1510'), 12 | ] 13 | 14 | operations = [ 15 | migrations.AlterField( 16 | model_name='blog', 17 | name='author', 18 | field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL), 19 | ), 20 | migrations.AlterField( 21 | model_name='blog', 22 | name='blog_type', 23 | field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='blog.BlogType'), 24 | ), 25 | ] 26 | -------------------------------------------------------------------------------- /mysite/blog/migrations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/able8/Django-Course/8cc6cabe7789117f37e009db842b8f8e41d8e5a5/mysite/blog/migrations/__init__.py -------------------------------------------------------------------------------- /mysite/blog/models.py: -------------------------------------------------------------------------------- 1 | from django.db import models 2 | from django.urls import reverse 3 | from django.contrib.auth.models import User 4 | from django.contrib.contenttypes.fields import GenericRelation 5 | from ckeditor_uploader.fields import RichTextUploadingField 6 | from read_statistics.models import ReadNumExpandMethod, ReadDetail 7 | 8 | 9 | # Create your models here. 10 | class BlogType(models.Model): 11 | type_name = models.CharField(max_length=15) 12 | 13 | def __str__(self): 14 | return self.type_name 15 | 16 | 17 | class Blog(models.Model, ReadNumExpandMethod): # 继承 方法 18 | title = models.CharField(max_length=50) 19 | blog_type = models.ForeignKey(BlogType, on_delete=models.CASCADE) 20 | content = RichTextUploadingField() 21 | author = models.ForeignKey(User, on_delete=models.CASCADE) 22 | # readed_num = models.IntegerField(default=0) 23 | read_details = GenericRelation(ReadDetail) 24 | created_time = models.DateTimeField(auto_now_add=True) 25 | last_updated_time = models.DateTimeField(auto_now=True) 26 | 27 | def get_url(self): 28 | return reverse('blog_detail', kwargs={'blog_pk': self.pk}) 29 | 30 | def get_email(self): 31 | return self.author.email 32 | 33 | def __str__(self): 34 | return '' % self.title 35 | 36 | class Meta: 37 | ordering = ['created_time'] -------------------------------------------------------------------------------- /mysite/blog/static/blog/blog.css: -------------------------------------------------------------------------------- 1 | ul.blog-types { 2 | list-style-type: none; 3 | } 4 | 5 | div.blog:not(:last-child) { 6 | margin-bottom: 2em; 7 | padding-bottom: 1em; 8 | border-bottom: 1px solid #eee; 9 | } 10 | 11 | div.blog h3 { 12 | margin-top: 0.3em; 13 | } 14 | 15 | div.blog p.blog-info { 16 | margin-bottom: 0; 17 | } 18 | 19 | ul.blog-info-description { 20 | list-style-type: none; 21 | margin-bottom: 1em; 22 | } 23 | 24 | ul.blog-info-description li{ 25 | display: inline-block; 26 | margin-right: 1em; 27 | } 28 | 29 | div.blog-content { 30 | text-indent: 2em; 31 | margin-bottom: 2em; 32 | } 33 | 34 | div.paginator { 35 | text-align: center; 36 | } 37 | 38 | div.blog-more { 39 | margin-top: 1em; 40 | } 41 | 42 | div.comment-area { 43 | margin-top: 2em; 44 | } 45 | 46 | h3.comment-area-title { 47 | border-bottom: 1px solid #ccc; 48 | padding-bottom: 0.4em; 49 | } 50 | 51 | div.django-ckeditor-widget { 52 | width: 100%; 53 | } 54 | 55 | div.comment { 56 | border-bottom: 1px dashed #ccc; 57 | margin-bottom: 0.5em; 58 | padding-bottom: 0.5em; 59 | } 60 | 61 | div.reply { 62 | margin-left: 2em; 63 | } 64 | 65 | div#reply_content_container { 66 | border: 1px solid #d1d1d1; 67 | border-bottom: none; 68 | background-color: #f8f8f8; 69 | overflow: hidden; 70 | padding: 1em 1em 0.5em; 71 | } 72 | 73 | p#reply_title { 74 | border-bottom: 1px dashed #ccc; 75 | padding-bottom: 0.5em; 76 | } 77 | 78 | div.like { 79 | color: #337ab7; 80 | cursor: pointer; 81 | display: inline-block; 82 | padding: 0.5em 0.3em; 83 | } 84 | 85 | div.like .active{ 86 | color: red; 87 | } -------------------------------------------------------------------------------- /mysite/blog/templates/blog/blog_detail.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | {% block title %} {{ blog.title }} {% endblock title %} 3 | {% block nav_blog_active %}active {% endblock nav_blog_active %} 4 | 5 | {% load staticfiles %} 6 | {% load comment_tags %} 7 | {% load likes_tags %} 8 | 9 | {% block header_extends %} 10 | 11 | 12 | 13 | {% endblock header_extends %} 14 | 15 | {% block content %} 16 |
17 |
18 |
19 |

{{ blog.title }}

20 |
    21 |
  • 作者: {{ blog.author }}
  • 22 |
  • 分类: {{ blog.blog_type }}
  • 23 |
  • 发布日期: {{ blog.created_time|date:'Y-m-d H:i:s' }}
  • 24 |
  • 阅读({{ blog.get_read_num }})
  • 25 |
  • 评论({% get_comment_count blog %})
  • 26 |
27 |
{{ blog.content|safe }}
28 | 29 | 34 | 35 |
36 |

上一篇: 37 | {% if previous_blog %} 38 | {{ previous_blog.title }} 39 | {% else %} 40 | 没有了 41 | {% endif %} 42 |

43 |

下一篇: 44 | {% if next_blog %} 45 | {{ next_blog.title }} 46 | {% else %} 47 | 没有了 48 | {% endif %} 49 |

50 |
51 |
52 |
53 |
54 |
55 |
56 |

提交评论

57 | {% if user.is_authenticated %} 58 |
59 | 60 | 64 | {% csrf_token %} 65 | {% get_comment_form blog as comment_form %} 66 | {% for field in comment_form %} 67 | {{ field }} 68 | {% endfor %} 69 | 70 | 71 |
72 | {% else %} 73 | 未登录,登录后方可评论 74 | 登录 75 | or 76 | 注册 77 | {% endif %} 78 |
79 |
80 |

评论列表

81 |
82 | {% get_comment_list blog as comments %} 83 | {% for comment in comments %} 84 |
85 | {{ comment.user.get_nickname_or_username }} 86 | ({{ comment.comment_time | date:"Y-m-d H:i:s" }}): 87 |
88 | {{ comment.text | safe }} 89 |
90 | 91 | 95 | 96 | 回复 97 | 98 | {% for reply in comment.root_comment.all %} 99 |
100 | {{ reply.user.get_nickname_or_username }} 101 | ({{ reply.comment_time | date:"Y-m-d H:i:s" }}): 102 | 回复 103 | {{ reply.reply_to.get_nickname_or_username }} 104 |
105 | {{ reply.text|safe }} 106 |
107 | 108 | 112 | 113 | 回复 114 |
115 | {% endfor %} 116 |
117 | {% empty %} 118 | 暂无评论 119 | {% endfor %} 120 |
121 |
122 |
123 |
124 | 125 |
126 | {% endblock content %} 127 | 128 | {# ajax 异步提交, 因为直接提交会刷新页面 #} 129 | {% block script_extends %} 130 | 287 | {% endblock script_extends %} -------------------------------------------------------------------------------- /mysite/blog/templates/blog/blog_list.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | {% block title %} 个人博客网站 {% endblock title %} 3 | {% block nav_blog_active%}active{% endblock%} 4 | 5 | {% load staticfiles %} 6 | {% load comment_tags %} 7 | {% load likes_tags %} 8 | 9 | {% block header_extends %} 10 | 11 | {% endblock header_extends %} 12 | 13 | {% block content %} 14 |
15 |
16 |
17 |
18 | 19 |
{% block blog_list_tilte %} 20 | 博客列表 21 | {% endblock blog_list_tilte %}
22 |
23 | {% for blog in blogs %} 24 |
25 |

26 | {{ blog.title }} 27 |

28 |
29 | {{ blog.blog_type }}    30 | 31 | {{ blog.created_time|date:'Y-m-d' }} 32 |   阅读({{ blog.get_read_num }}) 33 |   评论({% get_comment_count blog %}) 34 |   点赞({% get_like_count blog %}) 35 |
36 |

{{ blog.content|striptags|truncatechars:120 }}

37 |
38 | 39 | {% empty %} 40 |
41 |

暂无博客,敬请期待

42 |
43 | {% endfor %} 44 | 45 |
46 |
    47 |
  • 48 | 49 | {% if page_of_blogs.has_previous %} 50 | 51 | 52 | 53 | {% else %} 54 | 55 | {% endif %} 56 |
  • 57 | {# 全部页面 #} 58 | {% for page_num in page_range %} 59 | {% if page_num == page_of_blogs.number %} 60 |
  • {{ page_num }}
  • 61 | {% else %} 62 | {% if page_num == '...' %} 63 |
  • {{ page_num }}
  • 64 | {% else %} 65 |
  • {{ page_num }}
  • 66 | {% endif %} 67 | {% endif %} 68 | {% endfor %} 69 | 70 |
  • 71 | {# 下一页 #} 72 | {% if page_of_blogs.has_next %} 73 | 74 | 75 | 76 | {% else %} 77 | 78 | {% endif %} 79 | 80 |
  • 81 |
82 | 83 |

84 | 共有 {{ page_of_blogs.paginator.count }} 篇博客, 85 | 当前第 {{ page_of_blogs.number }} 页, 86 | 共 {{ page_of_blogs.paginator.num_pages }} 页。 87 |

88 |
89 | 90 |
91 |
92 |
93 | 94 | 124 |
125 |
126 | {% endblock content %} 127 | 128 | -------------------------------------------------------------------------------- /mysite/blog/templates/blog/blogs_with_date.html: -------------------------------------------------------------------------------- 1 | {% extends "blog/blog_list.html" %} 2 | {% block title %} 个人博客网站 {% endblock title %} 3 | 4 | {% block blog_list_tilte %} 5 | 日期归档:{{ blogs_with_date }} 6 | 查看全部博客 7 | {% endblock blog_list_tilte %} -------------------------------------------------------------------------------- /mysite/blog/templates/blog/blogs_with_type.html: -------------------------------------------------------------------------------- 1 | {% extends "blog/blog_list.html" %} 2 | {% block title %} 个人博客网站 {% endblock title %} 3 | 4 | {% block blog_list_tilte %} 5 | 分类:{{ blog_type.type_name }} 6 | 查看全部博客 7 | {% endblock blog_list_tilte %} -------------------------------------------------------------------------------- /mysite/blog/tests.py: -------------------------------------------------------------------------------- 1 | from django.test import TestCase 2 | 3 | # Create your tests here. 4 | -------------------------------------------------------------------------------- /mysite/blog/urls.py: -------------------------------------------------------------------------------- 1 | from django.urls import path 2 | from . import views 3 | 4 | # start with blog 5 | urlpatterns = [ 6 | # http://localhost:8000/blog/1 7 | path('', views.blog_list, name='blog_list'), 8 | path('', views.blog_detail, name='blog_detail'), 9 | path('type/', views.blogs_with_type, name='blogs_with_type'), 10 | path('date//', views.blogs_with_date, name='blogs_with_date'), 11 | ] 12 | -------------------------------------------------------------------------------- /mysite/blog/views.py: -------------------------------------------------------------------------------- 1 | from django.shortcuts import render, get_object_or_404 2 | from django.core.paginator import Paginator 3 | from django.conf import settings 4 | from django.db.models import Count 5 | from django.contrib.contenttypes.models import ContentType 6 | from .models import Blog, BlogType 7 | from read_statistics.utils import read_statistics_one_read 8 | 9 | 10 | def get_blog_list_common_date(request, blogs_all_list): 11 | paginator = Paginator(blogs_all_list, 12 | settings.EACH_PAGE_BLOGS_NUMBER) # 每2篇进行分页 13 | # print(dir(paginator)) 14 | page_num = request.GET.get('page', 1) # 获取url的页面参数(GET请求) 15 | page_of_blogs = paginator.get_page(page_num) 16 | current_page_num = page_of_blogs.number # 获取当前页码 17 | # 获取当前页的前后2页的页码范围 18 | page_range = [ 19 | x for x in range(current_page_num - 2, current_page_num + 3) 20 | if x in paginator.page_range 21 | ] 22 | 23 | # 加上省略号间隔页码 24 | if page_range[0] - 1 >= 2: 25 | page_range.insert(0, '...') 26 | if paginator.num_pages - page_range[-1] >= 2: 27 | page_range.append('...') 28 | # 加上首页和尾页 29 | if page_range[0] != 1: 30 | page_range.insert(0, 1) 31 | if page_range[-1] != paginator.num_pages: 32 | page_range.append(paginator.num_pages) 33 | 34 | # 获取博客分类的对应博客数量 35 | BlogType.objects.annotate(blog_count=Count('blog')) 36 | ''' 37 | blog_types = BlogType.objects.all() 38 | blog_types_list = [] 39 | for blog_type in blog_types: 40 | blog_type.blog_count = Blog.objects.filter(blog_type=blog_type).count() 41 | blog_types_list.append(blog_type) 42 | ''' 43 | 44 | # 获取日期归档对应的博客数量 45 | blog_dates = Blog.objects.dates('created_time', 'month', order='DESC') 46 | blog_dates_dict = {} 47 | for blog_date in blog_dates: 48 | blog_count = Blog.objects.filter( 49 | created_time__year=blog_date.year, 50 | created_time__month=blog_date.month).count() 51 | blog_dates_dict[blog_date] = blog_count 52 | 53 | context = {} 54 | context['blogs'] = page_of_blogs 55 | context['page_of_blogs'] = page_of_blogs 56 | context['page_range'] = page_range 57 | # context['blog_types'] = BlogType.objects.all() 58 | context['blog_types'] = BlogType.objects.annotate(blog_count=Count('blog')) 59 | # context['blog_dates'] = Blog.objects.dates('created_time', 'month', order='DESC') 60 | context['blog_dates'] = blog_dates_dict 61 | return context 62 | 63 | 64 | def blog_list(request): 65 | blogs_all_list = Blog.objects.all() 66 | context = get_blog_list_common_date(request, blogs_all_list) 67 | return render(request, 'blog/blog_list.html', context) 68 | 69 | 70 | def blogs_with_type(request, blog_type_pk): 71 | blog_type = get_object_or_404(BlogType, pk=blog_type_pk) 72 | blogs_all_list = Blog.objects.filter(blog_type=blog_type) 73 | context = get_blog_list_common_date(request, blogs_all_list) 74 | context['blog_type'] = blog_type 75 | return render(request, 'blog/blogs_with_type.html', context) 76 | 77 | 78 | def blogs_with_date(request, year, month): 79 | blogs_all_list = Blog.objects.filter( 80 | created_time__year=year, created_time__month=month) 81 | context = get_blog_list_common_date(request, blogs_all_list) 82 | context['blogs_with_date'] = '%s年%s月' % (year, month) 83 | return render(request, 'blog/blogs_with_date.html', context) 84 | 85 | 86 | def blog_detail(request, blog_pk): 87 | blog = get_object_or_404(Blog, pk=blog_pk) 88 | read_cookie_key = read_statistics_one_read(request, blog) 89 | 90 | context = {} 91 | context['previous_blog'] = Blog.objects.filter( 92 | created_time__gt=blog.created_time).last() 93 | context['next_blog'] = Blog.objects.filter( 94 | created_time__lt=blog.created_time).first() 95 | context['blog'] = blog 96 | response = render(request, 'blog/blog_detail.html', context) 97 | response.set_cookie(read_cookie_key, 'true') # 阅读cookie标记 98 | return response -------------------------------------------------------------------------------- /mysite/comment/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/able8/Django-Course/8cc6cabe7789117f37e009db842b8f8e41d8e5a5/mysite/comment/__init__.py -------------------------------------------------------------------------------- /mysite/comment/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | from .models import Comment 3 | 4 | 5 | @admin.register(Comment) 6 | class CommentAdmin(admin.ModelAdmin): 7 | list_display = ('id', 'content_object', 'text', 'comment_time', 'user') 8 | -------------------------------------------------------------------------------- /mysite/comment/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | 3 | 4 | class CommentConfig(AppConfig): 5 | name = 'comment' 6 | -------------------------------------------------------------------------------- /mysite/comment/forms.py: -------------------------------------------------------------------------------- 1 | from django import forms 2 | from django.contrib.contenttypes.models import ContentType 3 | from django.db.models import ObjectDoesNotExist 4 | from ckeditor.widgets import CKEditorWidget 5 | from .models import Comment 6 | 7 | 8 | class CommentForm(forms.Form): 9 | content_type = forms.CharField(widget=forms.HiddenInput) 10 | object_id = forms.IntegerField(widget=forms.HiddenInput) 11 | text = forms.CharField( 12 | widget=CKEditorWidget(config_name='comment_ckeditor'), 13 | error_messages={'required': '评论内容为空'}) 14 | 15 | reply_comment_id = forms.IntegerField( 16 | widget=forms.HiddenInput(attrs={'id': 'reply_comment_id'})) 17 | 18 | def __init__(self, *args, **kwargs): 19 | if 'user' in kwargs: 20 | self.user = kwargs.pop('user') # 接收用户信息, 并剔除,为了下一句不出错 21 | super(CommentForm, self).__init__(*args, **kwargs) 22 | 23 | # 验证数据 24 | def clean(self): 25 | # 判断用户是否登录 26 | if self.user.is_authenticated: 27 | self.cleaned_data['user'] = self.user 28 | else: 29 | raise forms.ValidationError('用户尚未登录') 30 | 31 | # 评论对象验证 32 | content_type = self.cleaned_data['content_type'] 33 | object_id = self.cleaned_data['object_id'] 34 | try: 35 | model_class = ContentType.objects.get( 36 | model=content_type).model_class() 37 | model_obj = model_class.objects.get(pk=object_id) 38 | self.cleaned_data['content_object'] = model_obj 39 | except ObjectDoesNotExist: 40 | raise forms.ValidationError('评论对象不存在') 41 | 42 | return self.cleaned_data 43 | 44 | # 验证提交的数据 45 | def clean_reply_comment_id(self): 46 | reply_comment_id = self.cleaned_data['reply_comment_id'] 47 | if reply_comment_id < 0: 48 | raise forms.ValidationError('回复出错') 49 | elif reply_comment_id == 0: 50 | self.cleaned_data['parent'] = None 51 | elif Comment.objects.filter(pk=reply_comment_id).exists(): 52 | self.cleaned_data['parent'] = Comment.objects.get( 53 | pk=reply_comment_id) 54 | else: 55 | raise forms.ValidationError('回复出错') 56 | return reply_comment_id -------------------------------------------------------------------------------- /mysite/comment/migrations/0001_initial.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.0.5 on 2018-08-23 06:43 2 | 3 | from django.conf import settings 4 | from django.db import migrations, models 5 | import django.db.models.deletion 6 | 7 | 8 | class Migration(migrations.Migration): 9 | 10 | initial = True 11 | 12 | dependencies = [ 13 | migrations.swappable_dependency(settings.AUTH_USER_MODEL), 14 | ('contenttypes', '0002_remove_content_type_name'), 15 | ] 16 | 17 | operations = [ 18 | migrations.CreateModel( 19 | name='Comment', 20 | fields=[ 21 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 22 | ('object_id', models.PositiveIntegerField()), 23 | ('text', models.TextField()), 24 | ('comment_time', models.DateTimeField(auto_now_add=True)), 25 | ('content_type', models.ForeignKey(on_delete=django.db.models.deletion.DO_NOTHING, to='contenttypes.ContentType')), 26 | ('user', models.ForeignKey(on_delete=django.db.models.deletion.DO_NOTHING, to=settings.AUTH_USER_MODEL)), 27 | ], 28 | ), 29 | ] 30 | -------------------------------------------------------------------------------- /mysite/comment/migrations/0002_auto_20180824_1532.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.0.5 on 2018-08-24 07:32 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('comment', '0001_initial'), 10 | ] 11 | 12 | operations = [ 13 | migrations.AlterModelOptions( 14 | name='comment', 15 | options={'ordering': ['-comment_time']}, 16 | ), 17 | migrations.AddField( 18 | model_name='comment', 19 | name='parent_id', 20 | field=models.IntegerField(default=0), 21 | ), 22 | ] 23 | -------------------------------------------------------------------------------- /mysite/comment/migrations/0003_auto_20180824_1539.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.0.5 on 2018-08-24 07:39 2 | 3 | from django.db import migrations, models 4 | import django.db.models.deletion 5 | 6 | 7 | class Migration(migrations.Migration): 8 | 9 | dependencies = [ 10 | ('comment', '0002_auto_20180824_1532'), 11 | ] 12 | 13 | operations = [ 14 | migrations.RemoveField( 15 | model_name='comment', 16 | name='parent_id', 17 | ), 18 | migrations.AddField( 19 | model_name='comment', 20 | name='parent', 21 | field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.DO_NOTHING, to='comment.Comment'), 22 | ), 23 | ] 24 | -------------------------------------------------------------------------------- /mysite/comment/migrations/0004_auto_20180824_1544.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.0.5 on 2018-08-24 07:44 2 | 3 | from django.conf import settings 4 | from django.db import migrations, models 5 | import django.db.models.deletion 6 | 7 | 8 | class Migration(migrations.Migration): 9 | 10 | dependencies = [ 11 | migrations.swappable_dependency(settings.AUTH_USER_MODEL), 12 | ('comment', '0003_auto_20180824_1539'), 13 | ] 14 | 15 | operations = [ 16 | migrations.AddField( 17 | model_name='comment', 18 | name='reply_to', 19 | field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.DO_NOTHING, related_name='replies', to=settings.AUTH_USER_MODEL), 20 | ), 21 | migrations.AlterField( 22 | model_name='comment', 23 | name='user', 24 | field=models.ForeignKey(on_delete=django.db.models.deletion.DO_NOTHING, related_name='comments', to=settings.AUTH_USER_MODEL), 25 | ), 26 | ] 27 | -------------------------------------------------------------------------------- /mysite/comment/migrations/0005_auto_20180824_1719.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.0.5 on 2018-08-24 09:19 2 | 3 | from django.db import migrations, models 4 | import django.db.models.deletion 5 | 6 | 7 | class Migration(migrations.Migration): 8 | 9 | dependencies = [ 10 | ('comment', '0004_auto_20180824_1544'), 11 | ] 12 | 13 | operations = [ 14 | migrations.AddField( 15 | model_name='comment', 16 | name='root', 17 | field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.DO_NOTHING, related_name='root_comment', to='comment.Comment'), 18 | ), 19 | migrations.AlterField( 20 | model_name='comment', 21 | name='parent', 22 | field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.DO_NOTHING, related_name='parent_comment', to='comment.Comment'), 23 | ), 24 | ] 25 | -------------------------------------------------------------------------------- /mysite/comment/migrations/0006_auto_20180825_1036.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.0.5 on 2018-08-25 02:36 2 | 3 | from django.conf import settings 4 | from django.db import migrations, models 5 | import django.db.models.deletion 6 | 7 | 8 | class Migration(migrations.Migration): 9 | 10 | dependencies = [ 11 | ('comment', '0005_auto_20180824_1719'), 12 | ] 13 | 14 | operations = [ 15 | migrations.AlterModelOptions( 16 | name='comment', 17 | options={'ordering': ['comment_time']}, 18 | ), 19 | migrations.AlterField( 20 | model_name='comment', 21 | name='content_type', 22 | field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='contenttypes.ContentType'), 23 | ), 24 | migrations.AlterField( 25 | model_name='comment', 26 | name='parent', 27 | field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='parent_comment', to='comment.Comment'), 28 | ), 29 | migrations.AlterField( 30 | model_name='comment', 31 | name='reply_to', 32 | field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='replies', to=settings.AUTH_USER_MODEL), 33 | ), 34 | migrations.AlterField( 35 | model_name='comment', 36 | name='root', 37 | field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='root_comment', to='comment.Comment'), 38 | ), 39 | migrations.AlterField( 40 | model_name='comment', 41 | name='user', 42 | field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='comments', to=settings.AUTH_USER_MODEL), 43 | ), 44 | ] 45 | -------------------------------------------------------------------------------- /mysite/comment/migrations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/able8/Django-Course/8cc6cabe7789117f37e009db842b8f8e41d8e5a5/mysite/comment/migrations/__init__.py -------------------------------------------------------------------------------- /mysite/comment/models.py: -------------------------------------------------------------------------------- 1 | import threading 2 | from django.db import models 3 | from django.contrib.contenttypes.fields import GenericForeignKey 4 | from django.contrib.contenttypes.models import ContentType 5 | from django.contrib.auth.models import User 6 | from django.conf import settings 7 | from django.core.mail import send_mail 8 | from django.template.loader import render_to_string 9 | 10 | 11 | # 多线程发送邮件 12 | class SendMail(threading.Thread): 13 | def __init__(self, subject, text, email, fail_silently=False): 14 | self.subject = subject 15 | self.text = text 16 | self.email = email 17 | self.fail_silently = fail_silently 18 | threading.Thread.__init__(self) 19 | 20 | def run(self): 21 | send_mail( 22 | self.subject, 23 | '', 24 | settings.EMAIL_HOST_USER, 25 | [self.email], 26 | fail_silently=self.fail_silently, 27 | html_message=self.text 28 | ) 29 | 30 | 31 | class Comment(models.Model): 32 | # 下面3行用来关联任意类型 33 | content_type = models.ForeignKey(ContentType, on_delete=models.CASCADE) 34 | object_id = models.PositiveIntegerField() 35 | content_object = GenericForeignKey('content_type', 'object_id') 36 | 37 | text = models.TextField() 38 | comment_time = models.DateTimeField(auto_now_add=True) 39 | user = models.ForeignKey( 40 | User, related_name='comments', on_delete=models.CASCADE) 41 | 42 | # parent_id = models.IntegerField(default=0) # 用于回复功能,或使用如下自己的外键 43 | root = models.ForeignKey( 44 | 'self', 45 | related_name='root_comment', 46 | null=True, 47 | on_delete=models.CASCADE) 48 | parent = models.ForeignKey( 49 | 'self', 50 | related_name='parent_comment', 51 | null=True, 52 | on_delete=models.CASCADE) 53 | reply_to = models.ForeignKey( 54 | User, related_name='replies', null=True, on_delete=models.CASCADE) 55 | 56 | def send_comment_mail(self): 57 | # 发送邮件通知 58 | if self.parent is None: 59 | # 评论我的博客 60 | # 发送邮箱 61 | subject = '有人评论你的博客' 62 | email = self.content_object.get_email() 63 | else: 64 | # 回复评论 65 | subject = '有人回复你的博客' 66 | email = self.reply_to.email 67 | if email != '': 68 | context = {} 69 | context['comment_text'] = self.text 70 | context['url'] = self.content_object.get_url() 71 | text = render_to_string('comment/send_mail.html', context) 72 | send_comment_thread = SendMail(subject, text, email) 73 | send_comment_thread.start() 74 | 75 | def __str__(self): 76 | return self.text # 显示评论内容 77 | 78 | class Meta: 79 | # ordering = ['-comment_time'] # 时间逆序,最新的在最前面 80 | ordering = ['comment_time'] # 时间逆序,最新的在最前面 81 | -------------------------------------------------------------------------------- /mysite/comment/templates/comment/send_mail.html: -------------------------------------------------------------------------------- 1 | 2 | {{ comment_text | safe }} 3 |
4 | 点击查看 5 | -------------------------------------------------------------------------------- /mysite/comment/templatetags/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/able8/Django-Course/8cc6cabe7789117f37e009db842b8f8e41d8e5a5/mysite/comment/templatetags/__init__.py -------------------------------------------------------------------------------- /mysite/comment/templatetags/comment_tags.py: -------------------------------------------------------------------------------- 1 | from django import template 2 | from django.contrib.contenttypes.models import ContentType 3 | from ..models import Comment 4 | from ..forms import CommentForm 5 | 6 | register = template.Library() 7 | 8 | 9 | @register.simple_tag 10 | def get_comment_count(obj): 11 | content_type = ContentType.objects.get_for_model( 12 | obj) # 根据具体对象获取contenttype 13 | return Comment.objects.filter( 14 | content_type=content_type, object_id=obj.pk).count() 15 | 16 | 17 | @register.simple_tag 18 | def get_comment_form(obj): 19 | content_type = ContentType.objects.get_for_model(obj) 20 | form = CommentForm(initial={ 21 | 'content_type': content_type, 22 | 'object_id': obj.pk, 23 | 'reply_comment_id': 0 24 | }) 25 | return form 26 | 27 | 28 | @register.simple_tag 29 | def get_comment_list(obj): 30 | content_type = ContentType.objects.get_for_model(obj) 31 | comments = comments = Comment.objects.filter( 32 | content_type=content_type, object_id=obj.pk, parent=None) 33 | return comments.order_by('-comment_time') -------------------------------------------------------------------------------- /mysite/comment/tests.py: -------------------------------------------------------------------------------- 1 | from django.test import TestCase 2 | 3 | # Create your tests here. 4 | -------------------------------------------------------------------------------- /mysite/comment/urls.py: -------------------------------------------------------------------------------- 1 | from django.urls import path 2 | from . import views 3 | 4 | 5 | urlpatterns = [ 6 | path('update_comment', views.update_comment, name='update_comment') 7 | ] 8 | -------------------------------------------------------------------------------- /mysite/comment/views.py: -------------------------------------------------------------------------------- 1 | from django.shortcuts import render, redirect 2 | from django.urls import reverse 3 | from django.http import JsonResponse 4 | from django.conf import settings 5 | from django.contrib.contenttypes.models import ContentType 6 | from .models import Comment 7 | from .forms import CommentForm 8 | ''' 9 | def update_comment(request): 10 | referer = request.META.get('HTTP_REFERER', reverse('home')) 11 | 12 | # 数据检查 13 | if not request.user.is_authenticated: 14 | return render(request, 'error.html', {'message': '请先登录', 'redirect_to': referer}) 15 | 16 | text = request.POST.get('text', '').strip() # 多个空格也是空内容 17 | if text == '': 18 | return render(request, 'error.html', {'message': '评论内容不能为空', 'redirect_to': referer}) 19 | 20 | try: 21 | content_type = request.POST.get('content_type', '') 22 | object_id = int(request.POST.get('object_id', '')) 23 | model_class = ContentType.objects.get(model=content_type).model_class() 24 | model_obj = model_class.objects.get(pk=object_id) 25 | except Exception as e: 26 | return render(request, 'error.html', {'message': '评论对象不存在', 'redirect_to': referer}) 27 | 28 | # 通过则保存数据 29 | comment = Comment() 30 | comment.user = request.user 31 | comment.text = text 32 | comment.content_object = model_obj 33 | comment.save() 34 | return redirect(referer) # 提交后重定向到原页面 35 | ''' 36 | 37 | 38 | def update_comment(request): 39 | # referer = request.META.get('HTTP_REFERER', reverse('home')) 40 | comment_form = CommentForm( 41 | request.POST, user=request.user) # 实例化, 传递了用户信息,直接有表单类验证登录 42 | data = {} 43 | if comment_form.is_valid(): 44 | # 通过则保存数据 45 | comment = Comment() 46 | comment.user = comment_form.cleaned_data['user'] 47 | comment.text = comment_form.cleaned_data['text'] 48 | comment.content_object = comment_form.cleaned_data['content_object'] 49 | 50 | # 判断回复 51 | parent = comment_form.cleaned_data['parent'] 52 | if parent is not None: 53 | comment.root = parent.root if parent.root is not None else parent 54 | comment.parent = parent 55 | comment.reply_to = parent.user 56 | comment.save() 57 | 58 | # 发送邮件通知 59 | comment.send_comment_mail() 60 | 61 | # 返回数据 62 | data['status'] = 'SUCCESS' 63 | data['username'] = comment.user.get_nickname_or_username() 64 | # data['comment_time'] = comment.comment_time.strftime( 65 | # '%Y-%m-%d %H:%M:%S') 66 | data['comment_time'] = comment.comment_time.timestamp() 67 | data['text'] = comment.text 68 | data['content_type'] = ContentType.objects.get_for_model(comment).model 69 | if parent is not None: 70 | data['reply_to'] = comment.reply_to.get_nickname_or_username() 71 | else: 72 | data['reply_to'] = '' 73 | data['pk'] = comment.pk 74 | data['root_pk'] = comment.root.pk if comment.root is not None else '' 75 | 76 | else: 77 | data['status'] = 'ERROR' 78 | data['message'] = list(comment_form.errors.values())[0][0] 79 | return JsonResponse(data) -------------------------------------------------------------------------------- /mysite/db.sqlite3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/able8/Django-Course/8cc6cabe7789117f37e009db842b8f8e41d8e5a5/mysite/db.sqlite3 -------------------------------------------------------------------------------- /mysite/likes/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/able8/Django-Course/8cc6cabe7789117f37e009db842b8f8e41d8e5a5/mysite/likes/__init__.py -------------------------------------------------------------------------------- /mysite/likes/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | 3 | # Register your models here. 4 | -------------------------------------------------------------------------------- /mysite/likes/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | 3 | 4 | class LikesConfig(AppConfig): 5 | name = 'likes' 6 | -------------------------------------------------------------------------------- /mysite/likes/migrations/0001_initial.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.0.5 on 2018-08-25 02:36 2 | 3 | from django.conf import settings 4 | from django.db import migrations, models 5 | import django.db.models.deletion 6 | 7 | 8 | class Migration(migrations.Migration): 9 | 10 | initial = True 11 | 12 | dependencies = [ 13 | ('contenttypes', '0002_remove_content_type_name'), 14 | migrations.swappable_dependency(settings.AUTH_USER_MODEL), 15 | ] 16 | 17 | operations = [ 18 | migrations.CreateModel( 19 | name='LikeCount', 20 | fields=[ 21 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 22 | ('object_id', models.PositiveIntegerField()), 23 | ('liked_num', models.IntegerField(default=0)), 24 | ('content_type', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='contenttypes.ContentType')), 25 | ], 26 | ), 27 | migrations.CreateModel( 28 | name='LikeRecode', 29 | fields=[ 30 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 31 | ('object_id', models.PositiveIntegerField()), 32 | ('liked_time', models.DateTimeField(auto_now_add=True)), 33 | ('content_type', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='contenttypes.ContentType')), 34 | ('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)), 35 | ], 36 | ), 37 | ] 38 | -------------------------------------------------------------------------------- /mysite/likes/migrations/0002_auto_20180825_1238.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.0.5 on 2018-08-25 04:38 2 | 3 | from django.conf import settings 4 | from django.db import migrations 5 | 6 | 7 | class Migration(migrations.Migration): 8 | 9 | dependencies = [ 10 | ('contenttypes', '0002_remove_content_type_name'), 11 | migrations.swappable_dependency(settings.AUTH_USER_MODEL), 12 | ('likes', '0001_initial'), 13 | ] 14 | 15 | operations = [ 16 | migrations.RenameModel( 17 | old_name='LikeRecode', 18 | new_name='LikeRecord', 19 | ), 20 | ] 21 | -------------------------------------------------------------------------------- /mysite/likes/migrations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/able8/Django-Course/8cc6cabe7789117f37e009db842b8f8e41d8e5a5/mysite/likes/migrations/__init__.py -------------------------------------------------------------------------------- /mysite/likes/models.py: -------------------------------------------------------------------------------- 1 | from django.db import models 2 | from django.contrib.contenttypes.fields import GenericForeignKey 3 | from django.contrib.contenttypes.models import ContentType 4 | from django.contrib.auth.models import User 5 | 6 | 7 | class LikeCount(models.Model): 8 | content_type = models.ForeignKey(ContentType, on_delete=models.CASCADE) 9 | object_id = models.PositiveIntegerField() 10 | content_object = GenericForeignKey('content_type', 'object_id') 11 | 12 | liked_num = models.IntegerField(default=0) 13 | 14 | class LikeRecord(models.Model): 15 | content_type = models.ForeignKey(ContentType, on_delete=models.CASCADE) 16 | object_id = models.PositiveIntegerField() 17 | content_object = GenericForeignKey('content_type', 'object_id') 18 | 19 | user = models.ForeignKey(User, on_delete=models.CASCADE) 20 | liked_time = models.DateTimeField(auto_now_add=True) -------------------------------------------------------------------------------- /mysite/likes/templatetags/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/able8/Django-Course/8cc6cabe7789117f37e009db842b8f8e41d8e5a5/mysite/likes/templatetags/__init__.py -------------------------------------------------------------------------------- /mysite/likes/templatetags/likes_tags.py: -------------------------------------------------------------------------------- 1 | from django import template 2 | from django.contrib.contenttypes.models import ContentType 3 | from ..models import LikeCount, LikeRecord 4 | 5 | register = template.Library() 6 | 7 | 8 | @register.simple_tag 9 | def get_like_count(obj): 10 | content_type = ContentType.objects.get_for_model(obj) 11 | like_count, created = LikeCount.objects.get_or_create( 12 | content_type=content_type, object_id=obj.pk) 13 | return like_count.liked_num 14 | 15 | 16 | @register.simple_tag(takes_context=True) # 使用模版里面的变量 17 | def get_like_status(context, obj): 18 | content_type = ContentType.objects.get_for_model(obj) 19 | user = context['user'] 20 | if not user.is_authenticated: 21 | return '' 22 | if LikeRecord.objects.filter( 23 | content_type=content_type, object_id=obj.pk, user=user).exists(): 24 | return 'active' 25 | else: 26 | return '' 27 | 28 | @register.simple_tag 29 | def get_content_type(obj): 30 | content_type = ContentType.objects.get_for_model(obj) 31 | return content_type.model -------------------------------------------------------------------------------- /mysite/likes/tests.py: -------------------------------------------------------------------------------- 1 | from django.test import TestCase 2 | 3 | # Create your tests here. 4 | -------------------------------------------------------------------------------- /mysite/likes/urls.py: -------------------------------------------------------------------------------- 1 | from django.urls import path 2 | from . import views 3 | 4 | 5 | urlpatterns = [ 6 | path('like_change', views.like_change, name='like_change') 7 | ] 8 | -------------------------------------------------------------------------------- /mysite/likes/views.py: -------------------------------------------------------------------------------- 1 | from django.shortcuts import render 2 | from django.contrib.contenttypes.models import ContentType 3 | from django.http import JsonResponse 4 | from django.db.models import ObjectDoesNotExist 5 | from .models import LikeCount, LikeRecord 6 | 7 | 8 | def ErrorResponse(code, message): 9 | data = {} 10 | data['status'] = 'ERROR' 11 | data['code'] = code 12 | data['message'] = message 13 | return JsonResponse(data) 14 | 15 | 16 | def SuccessResponse(liked_num): 17 | data = {} 18 | data['status'] = 'SUCCESS' 19 | data['liked_num'] = liked_num 20 | return JsonResponse(data) 21 | 22 | 23 | def like_change(request): 24 | # 获取请求传递的数据 25 | # 获取用户,验证用户登录 26 | user = request.user 27 | if not user.is_authenticated: 28 | return ErrorResponse(400, 'you were not login') 29 | 30 | content_type = request.GET.get('content_type') 31 | object_id = int(request.GET.get('object_id')) 32 | 33 | try: 34 | content_type = ContentType.objects.get(model=content_type) 35 | model_class = content_type.model_class() 36 | model_obj = model_class.objects.get(pk=object_id) 37 | except ObjectDoesNotExist: 38 | return ErrorResponse(401, 'object not exist') 39 | 40 | # 处理数据 41 | if request.GET.get('is_like') == 'true': 42 | # 要点赞 43 | like_record, created = LikeRecord.objects.get_or_create(content_type=content_type, object_id=object_id, user=user) 44 | if created: 45 | # 未点赞过,点赞数加1 46 | like_count, created = LikeCount.objects.get_or_create(content_type=content_type, object_id=object_id) 47 | like_count.liked_num += 1 48 | like_count.save() 49 | return SuccessResponse(like_count.liked_num) 50 | else: 51 | # 已点赞过,不能重复点赞 52 | return ErrorResponse(402, 'you were liked') 53 | else: 54 | # 取消点赞 55 | if LikeRecord.objects.filter(content_type=content_type, object_id=object_id, user=user): 56 | # 有点赞,取消点赞 57 | like_record = LikeRecord.objects.get(content_type=content_type, object_id=object_id, user=user) 58 | like_record.delete() 59 | # 点赞总数 -1 60 | like_count, created = LikeCount.objects.get_or_create(content_type=content_type, object_id=object_id) 61 | if not created: 62 | like_count.liked_num -= 1 63 | like_count.save() 64 | return SuccessResponse(like_count.liked_num) 65 | else: 66 | return ErrorResponse(404, 'data error') 67 | else: 68 | # 没点赞过,不能取消 69 | return ErrorResponse(403, 'you were not liked') -------------------------------------------------------------------------------- /mysite/manage.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | import os 3 | import sys 4 | 5 | if __name__ == "__main__": 6 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "mysite.settings") 7 | try: 8 | from django.core.management import execute_from_command_line 9 | except ImportError as exc: 10 | raise ImportError( 11 | "Couldn't import Django. Are you sure it's installed and " 12 | "available on your PYTHONPATH environment variable? Did you " 13 | "forget to activate a virtual environment?" 14 | ) from exc 15 | execute_from_command_line(sys.argv) 16 | -------------------------------------------------------------------------------- /mysite/media/upload/2018/08/22/wechatimg95.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/able8/Django-Course/8cc6cabe7789117f37e009db842b8f8e41d8e5a5/mysite/media/upload/2018/08/22/wechatimg95.jpeg -------------------------------------------------------------------------------- /mysite/mysite/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/able8/Django-Course/8cc6cabe7789117f37e009db842b8f8e41d8e5a5/mysite/mysite/__init__.py -------------------------------------------------------------------------------- /mysite/mysite/settings.py: -------------------------------------------------------------------------------- 1 | """ 2 | Django settings for mysite project. 3 | 4 | Generated by 'django-admin startproject' using Django 2.0.5. 5 | 6 | For more information on this file, see 7 | https://docs.djangoproject.com/en/2.0/topics/settings/ 8 | 9 | For the full list of settings and their values, see 10 | https://docs.djangoproject.com/en/2.0/ref/settings/ 11 | """ 12 | 13 | import os 14 | 15 | # Build paths inside the project like this: os.path.join(BASE_DIR, ...) 16 | BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) 17 | 18 | 19 | # Quick-start development settings - unsuitable for production 20 | # See https://docs.djangoproject.com/en/2.0/howto/deployment/checklist/ 21 | 22 | # SECURITY WARNING: keep the secret key used in production secret! 23 | SECRET_KEY = '6w4kbq@&=+ar00--v^&rjorv-uum%#v5#rstnbmi!&%a#ih@^l' 24 | 25 | # SECURITY WARNING: don't run with debug turned on in production! 26 | DEBUG = True 27 | 28 | ALLOWED_HOSTS = [] 29 | 30 | 31 | # Application definition 32 | 33 | INSTALLED_APPS = [ 34 | 'django.contrib.admin', 35 | 'django.contrib.auth', 36 | 'django.contrib.contenttypes', 37 | 'django.contrib.sessions', 38 | 'django.contrib.messages', 39 | 'django.contrib.staticfiles', 40 | 'ckeditor', 41 | 'ckeditor_uploader', 42 | 'blog', 43 | 'read_statistics', 44 | 'comment', 45 | 'likes', 46 | 'user', 47 | ] 48 | 49 | MIDDLEWARE = [ 50 | 'django.middleware.security.SecurityMiddleware', 51 | 'django.contrib.sessions.middleware.SessionMiddleware', 52 | 'django.middleware.common.CommonMiddleware', 53 | 'django.middleware.csrf.CsrfViewMiddleware', 54 | 'django.contrib.auth.middleware.AuthenticationMiddleware', 55 | 'django.contrib.messages.middleware.MessageMiddleware', 56 | 'django.middleware.clickjacking.XFrameOptionsMiddleware', 57 | ] 58 | 59 | ROOT_URLCONF = 'mysite.urls' 60 | 61 | TEMPLATES = [ 62 | { 63 | 'BACKEND': 'django.template.backends.django.DjangoTemplates', 64 | 'DIRS': [ 65 | os.path.join(BASE_DIR, 'templates'), 66 | ], 67 | 'APP_DIRS': True, 68 | 'OPTIONS': { 69 | 'context_processors': [ 70 | 'django.template.context_processors.debug', 71 | 'django.template.context_processors.request', 72 | 'django.contrib.auth.context_processors.auth', 73 | 'django.contrib.messages.context_processors.messages', 74 | 'user.context_processors.login_modal_form', 75 | ], 76 | }, 77 | }, 78 | ] 79 | 80 | WSGI_APPLICATION = 'mysite.wsgi.application' 81 | 82 | 83 | # Database 84 | # https://docs.djangoproject.com/en/2.0/ref/settings/#databases 85 | 86 | ''' 87 | DATABASES = { 88 | 'default': { 89 | 'ENGINE': 'django.db.backends.sqlite3', 90 | 'NAME': os.path.join(BASE_DIR, 'db.sqlite3'), 91 | } 92 | } 93 | 94 | ''' 95 | 96 | DATABASES = { 97 | 'default': { 98 | 'ENGINE': 'django.db.backends.mysql', 99 | 'NAME': 'mysite_db', 100 | 'USER': 'able', 101 | 'PASSWORD': 'pwd123456', 102 | 'HOST': '127.0.0.1', 103 | 'PORT': '3306', 104 | } 105 | } 106 | 107 | # Password validation 108 | # https://docs.djangoproject.com/en/2.0/ref/settings/#auth-password-validators 109 | 110 | AUTH_PASSWORD_VALIDATORS = [ 111 | { 112 | 'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator', 113 | }, 114 | { 115 | 'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator', 116 | }, 117 | { 118 | 'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator', 119 | }, 120 | { 121 | 'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator', 122 | }, 123 | ] 124 | 125 | 126 | # Internationalization 127 | # https://docs.djangoproject.com/en/2.0/topics/i18n/ 128 | 129 | # LANGUAGE_CODE = 'en-us' 130 | LANGUAGE_CODE = 'zh-hans' 131 | 132 | # TIME_ZONE = 'UTC' 133 | TIME_ZONE = 'Asia/Shanghai' 134 | 135 | USE_I18N = True 136 | 137 | USE_L10N = True 138 | 139 | USE_TZ = True 140 | 141 | 142 | # Static files (CSS, JavaScript, Images) 143 | # https://docs.djangoproject.com/en/2.0/howto/static-files/ 144 | 145 | STATIC_URL = '/static/' 146 | 147 | STATICFILES_DIRS = [ 148 | os.path.join(BASE_DIR, 'static'), 149 | ] 150 | 151 | # media 152 | MEDIA_URL = '/media/' 153 | MEDIA_ROOT = os.path.join(BASE_DIR, 'media') 154 | 155 | # 配置ckeditor 156 | CKEDITOR_UPLOAD_PATH = 'upload/' 157 | 158 | # 配置ckeditor评论表单 159 | CKEDITOR_CONFIGS = { 160 | 'default': {}, 161 | 'comment_ckeditor': { 162 | 'toolbar': 'custom', 163 | 'toolbar_custom': [ 164 | ['Bold', 'Italic', 'Underline', 'Strike', 'Subscript', 'Superscript'], 165 | ['TextColor', 'BGColor', 'RemoveFormat'], 166 | ['NumberedList', 'BulletedList'], 167 | ['Link', 'Unlink'], 168 | ['Smiley', 'SpecialChar', 'Blockquote'], 169 | ], 170 | 'width': 'auto', 171 | 'height': '180', 172 | 'tabspace': 4, 173 | 'removePlugins': 'elementspath', 174 | 'resize_enable': False, 175 | } 176 | } 177 | 178 | # 自定义参数 179 | EACH_PAGE_BLOGS_NUMBER = 3 180 | 181 | # 缓存设置 182 | CACHES = { 183 | 'default': { 184 | 'BACKEND': 'django.core.cache.backends.db.DatabaseCache', 185 | 'LOCATION': 'my_cache_table', # 缓存表名 186 | } 187 | } 188 | 189 | 190 | # 发送邮件设置 191 | # https://docs.djangoproject.com/en/2.0/ref/settings/#email 192 | # https://docs.djangoproject.com/en/2.0/topics/email/ 193 | 194 | EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend' 195 | EMAIL_HOST = 'smtp.qq.com' 196 | EMAIL_PORT = '25' 197 | 198 | EMAIL_SUBJECT_PREFIX = '[able的博客]' 199 | EMAIL_USE_TLS = True # 与smtp服务器通信时,是否启动TLS链接 安全链接 -------------------------------------------------------------------------------- /mysite/mysite/urls.py: -------------------------------------------------------------------------------- 1 | """mysite URL Configuration 2 | 3 | The `urlpatterns` list routes URLs to views. For more information please see: 4 | https://docs.djangoproject.com/en/2.0/topics/http/urls/ 5 | Examples: 6 | Function views 7 | 1. Add an import: from my_app import views 8 | 2. Add a URL to urlpatterns: path('', views.home, name='home') 9 | Class-based views 10 | 1. Add an import: from other_app.views import Home 11 | 2. Add a URL to urlpatterns: path('', Home.as_view(), name='home') 12 | Including another URLconf 13 | 1. Import the include() function: from django.urls import include, path 14 | 2. Add a URL to urlpatterns: path('blog/', include('blog.urls')) 15 | """ 16 | from django.contrib import admin 17 | from django.urls import path, include 18 | from django.conf import settings 19 | from django.conf.urls.static import static 20 | # from blog.views import blog_list 21 | from . import views 22 | 23 | urlpatterns = [ 24 | path('', views.home, name='home'), 25 | path('admin/', admin.site.urls), 26 | path('ckeditor', include('ckeditor_uploader.urls')), 27 | path('blog/', include('blog.urls')), 28 | path('comment/', include('comment.urls')), 29 | path('likes/', include('likes.urls')), 30 | path('user/', include('user.urls')), 31 | ] 32 | 33 | urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT) -------------------------------------------------------------------------------- /mysite/mysite/views.py: -------------------------------------------------------------------------------- 1 | from django.shortcuts import render 2 | from django.core.cache import cache 3 | from django.contrib.contenttypes.models import ContentType 4 | from read_statistics.utils import get_seven_days_read_data, get_today_hot_data, get_yesterday_hot_data, get_7_days_hot_data 5 | from blog.models import Blog 6 | 7 | 8 | def home(request): 9 | blog_content_type = ContentType.objects.get_for_model(Blog) 10 | dates, read_nums = get_seven_days_read_data(blog_content_type) 11 | today_hot_data = get_today_hot_data(blog_content_type) 12 | yesterday_hot_data = get_yesterday_hot_data(blog_content_type) 13 | 14 | # 获取7天热门博客的缓存数据 15 | hot_data_for_7_days = cache.get('hot_data_for_7_days') 16 | if hot_data_for_7_days is None: 17 | hot_data_for_7_days = get_7_days_hot_data(blog_content_type) 18 | cache.set('hot_data_for_7_days', hot_data_for_7_days, 20) 19 | print('calc') 20 | else: 21 | print('use cache') 22 | 23 | context = {} 24 | context['dates'] = dates 25 | context['read_nums'] = read_nums 26 | context['today_hot_data'] = today_hot_data 27 | context['yesterday_hot_data'] = yesterday_hot_data 28 | context['hot_data_for_7_days'] = hot_data_for_7_days 29 | return render(request, 'home.html', context) 30 | -------------------------------------------------------------------------------- /mysite/mysite/wsgi.py: -------------------------------------------------------------------------------- 1 | """ 2 | WSGI config for mysite project. 3 | 4 | It exposes the WSGI callable as a module-level variable named ``application``. 5 | 6 | For more information on this file, see 7 | https://docs.djangoproject.com/en/2.0/howto/deployment/wsgi/ 8 | """ 9 | 10 | import os 11 | 12 | from django.core.wsgi import get_wsgi_application 13 | 14 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "mysite.settings") 15 | 16 | application = get_wsgi_application() 17 | -------------------------------------------------------------------------------- /mysite/read_statistics/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/able8/Django-Course/8cc6cabe7789117f37e009db842b8f8e41d8e5a5/mysite/read_statistics/__init__.py -------------------------------------------------------------------------------- /mysite/read_statistics/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | from .models import ReadNum, ReadDetail 3 | 4 | 5 | @admin.register(ReadNum) 6 | class ReadNumAdmin(admin.ModelAdmin): 7 | list_display = ('read_num', 'content_object') 8 | 9 | 10 | @admin.register(ReadDetail) 11 | class ReadDetailAdmin(admin.ModelAdmin): 12 | list_display = ('date', 'read_num', 'content_object') -------------------------------------------------------------------------------- /mysite/read_statistics/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | 3 | 4 | class ReadStatisticsConfig(AppConfig): 5 | name = 'read_statistics' 6 | -------------------------------------------------------------------------------- /mysite/read_statistics/migrations/0001_initial.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.0.5 on 2018-08-22 07:10 2 | 3 | from django.db import migrations, models 4 | import django.db.models.deletion 5 | 6 | 7 | class Migration(migrations.Migration): 8 | 9 | initial = True 10 | 11 | dependencies = [ 12 | ('contenttypes', '0002_remove_content_type_name'), 13 | ] 14 | 15 | operations = [ 16 | migrations.CreateModel( 17 | name='ReadNum', 18 | fields=[ 19 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 20 | ('read_num', models.IntegerField(default=0)), 21 | ('object_id', models.PositiveIntegerField()), 22 | ('content_type', models.ForeignKey(on_delete=django.db.models.deletion.DO_NOTHING, to='contenttypes.ContentType')), 23 | ], 24 | ), 25 | ] 26 | -------------------------------------------------------------------------------- /mysite/read_statistics/migrations/0002_readdetail.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.0.5 on 2018-08-23 00:15 2 | 3 | from django.db import migrations, models 4 | import django.db.models.deletion 5 | import django.utils.timezone 6 | 7 | 8 | class Migration(migrations.Migration): 9 | 10 | dependencies = [ 11 | ('contenttypes', '0002_remove_content_type_name'), 12 | ('read_statistics', '0001_initial'), 13 | ] 14 | 15 | operations = [ 16 | migrations.CreateModel( 17 | name='ReadDetail', 18 | fields=[ 19 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 20 | ('date', models.DateField(default=django.utils.timezone.now)), 21 | ('read_num', models.IntegerField(default=0)), 22 | ('object_id', models.PositiveIntegerField()), 23 | ('content_type', models.ForeignKey(on_delete=django.db.models.deletion.DO_NOTHING, to='contenttypes.ContentType')), 24 | ], 25 | ), 26 | ] 27 | -------------------------------------------------------------------------------- /mysite/read_statistics/migrations/0003_auto_20180825_1036.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.0.5 on 2018-08-25 02:36 2 | 3 | from django.db import migrations, models 4 | import django.db.models.deletion 5 | 6 | 7 | class Migration(migrations.Migration): 8 | 9 | dependencies = [ 10 | ('read_statistics', '0002_readdetail'), 11 | ] 12 | 13 | operations = [ 14 | migrations.AlterField( 15 | model_name='readdetail', 16 | name='content_type', 17 | field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='contenttypes.ContentType'), 18 | ), 19 | migrations.AlterField( 20 | model_name='readnum', 21 | name='content_type', 22 | field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='contenttypes.ContentType'), 23 | ), 24 | ] 25 | -------------------------------------------------------------------------------- /mysite/read_statistics/migrations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/able8/Django-Course/8cc6cabe7789117f37e009db842b8f8e41d8e5a5/mysite/read_statistics/migrations/__init__.py -------------------------------------------------------------------------------- /mysite/read_statistics/models.py: -------------------------------------------------------------------------------- 1 | from django.db import models 2 | from django.db.models.fields import exceptions 3 | from django.contrib.contenttypes.fields import GenericForeignKey 4 | from django.contrib.contenttypes.models import ContentType 5 | from django.utils import timezone 6 | 7 | 8 | class ReadNum(models.Model): 9 | read_num = models.IntegerField(default=0) 10 | 11 | content_type = models.ForeignKey(ContentType, on_delete=models.CASCADE) 12 | object_id = models.PositiveIntegerField() 13 | content_object = GenericForeignKey('content_type', 'object_id') 14 | 15 | 16 | class ReadNumExpandMethod(): 17 | def get_read_num(self): 18 | try: 19 | ct = ContentType.objects.get_for_model(self) 20 | readnum = ReadNum.objects.get(content_type=ct, object_id=self.pk) 21 | return readnum.read_num 22 | except exceptions.ObjectDoesNotExist: 23 | return 0 24 | 25 | 26 | class ReadDetail(models.Model): 27 | date = models.DateField(default=timezone.now) 28 | read_num = models.IntegerField(default=0) 29 | 30 | content_type = models.ForeignKey(ContentType, on_delete=models.CASCADE) 31 | object_id = models.PositiveIntegerField() 32 | content_object = GenericForeignKey('content_type', 'object_id') -------------------------------------------------------------------------------- /mysite/read_statistics/tests.py: -------------------------------------------------------------------------------- 1 | from django.test import TestCase 2 | 3 | # Create your tests here. 4 | -------------------------------------------------------------------------------- /mysite/read_statistics/utils.py: -------------------------------------------------------------------------------- 1 | import datetime 2 | from django.db.models import Sum 3 | from django.contrib.contenttypes.models import ContentType 4 | from django.utils import timezone 5 | from .models import ReadNum, ReadDetail 6 | from blog.models import Blog 7 | 8 | 9 | def read_statistics_one_read(request, obj): 10 | ct = ContentType.objects.get_for_model(obj) 11 | key = '%s_%s_read' % (ct.model, obj.pk) # cookie key 12 | 13 | if not request.COOKIES.get(key): 14 | # 总阅读量 + 1 15 | readnum, created = ReadNum.objects.get_or_create( 16 | content_type=ct, object_id=obj.pk) 17 | readnum.read_num += 1 18 | readnum.save() 19 | 20 | # 每天阅读量 + 1 21 | date = timezone.now().date() 22 | readDetail, created = ReadDetail.objects.get_or_create( 23 | content_type=ct, object_id=obj.pk, date=date) 24 | readDetail.read_num += 1 25 | readDetail.save() 26 | return key 27 | 28 | 29 | # 统计最近7天阅读量 30 | def get_seven_days_read_data(content_type): 31 | today = timezone.now().date() 32 | dates = [] 33 | read_nums = [] 34 | for i in range(6, -1, -1): 35 | date = today - datetime.timedelta(days=i) 36 | dates.append(date.strftime('%m/%d')) 37 | read_details = ReadDetail.objects.filter( 38 | content_type=content_type, date=date) 39 | result = read_details.aggregate(read_num_sum=Sum('read_num')) # 聚合 40 | read_nums.append(result['read_num_sum'] or 0) # 空则为0 41 | return dates, read_nums 42 | 43 | 44 | # 获取今日热门文章 45 | def get_today_hot_data(content_type): 46 | today = timezone.now().date() 47 | read_details = ReadDetail.objects.filter( 48 | content_type=content_type, date=today).order_by('-read_num') 49 | return read_details[:7] # 取前7条 50 | 51 | 52 | # 获取昨天热门文章 53 | def get_yesterday_hot_data(content_type): 54 | today = timezone.now().date() 55 | yesterday = today - datetime.timedelta(days=1) 56 | read_details = ReadDetail.objects.filter( 57 | content_type=content_type, date=yesterday).order_by('-read_num') 58 | return read_details[:7] 59 | 60 | 61 | # 获取7天热门文章 62 | def get_7_days_hot_data(content_type): 63 | today = timezone.now().date() 64 | date = today - datetime.timedelta(days=7) 65 | blogs = Blog.objects\ 66 | .filter(read_details__date__lt=today, read_details__date__gte=date)\ 67 | .values('id', 'title')\ 68 | .annotate(read_num_sum=Sum('read_details__read_num'))\ 69 | .order_by('-read_num_sum') 70 | return blogs[:7] 71 | -------------------------------------------------------------------------------- /mysite/read_statistics/views.py: -------------------------------------------------------------------------------- 1 | from django.shortcuts import render 2 | 3 | # Create your views here. 4 | -------------------------------------------------------------------------------- /mysite/static/base.css: -------------------------------------------------------------------------------- 1 | * { 2 | margin: 0; 3 | padding: 0; 4 | } 5 | 6 | body { 7 | margin-top: 70px!important; 8 | } 9 | 10 | ul { 11 | list-style: none; 12 | } 13 | 14 | ul.navbar-right { 15 | margin-right: 20px; 16 | } 17 | 18 | ul.dropdown-menu { 19 | min-width: 100px; 20 | } 21 | /* div.nav{ 22 | background-color: #eee; 23 | border-bottom: 1px solid #ccc; 24 | padding: 10px 5px; 25 | 26 | } 27 | div.nav a{ 28 | text-decoration: none; 29 | color: #000; 30 | padding: 5px 10px; 31 | } 32 | div.nav a.logo { 33 | display: inline-block; 34 | } */ -------------------------------------------------------------------------------- /mysite/static/bootstrap-3.3.7/fonts/glyphicons-halflings-regular.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/able8/Django-Course/8cc6cabe7789117f37e009db842b8f8e41d8e5a5/mysite/static/bootstrap-3.3.7/fonts/glyphicons-halflings-regular.eot -------------------------------------------------------------------------------- /mysite/static/bootstrap-3.3.7/fonts/glyphicons-halflings-regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/able8/Django-Course/8cc6cabe7789117f37e009db842b8f8e41d8e5a5/mysite/static/bootstrap-3.3.7/fonts/glyphicons-halflings-regular.ttf -------------------------------------------------------------------------------- /mysite/static/bootstrap-3.3.7/fonts/glyphicons-halflings-regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/able8/Django-Course/8cc6cabe7789117f37e009db842b8f8e41d8e5a5/mysite/static/bootstrap-3.3.7/fonts/glyphicons-halflings-regular.woff -------------------------------------------------------------------------------- /mysite/static/bootstrap-3.3.7/fonts/glyphicons-halflings-regular.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/able8/Django-Course/8cc6cabe7789117f37e009db842b8f8e41d8e5a5/mysite/static/bootstrap-3.3.7/fonts/glyphicons-halflings-regular.woff2 -------------------------------------------------------------------------------- /mysite/static/bootstrap-3.3.7/js/bootstrap.min.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * Bootstrap v3.3.7 (http://getbootstrap.com) 3 | * Copyright 2011-2016 Twitter, Inc. 4 | * Licensed under the MIT license 5 | */ 6 | if("undefined"==typeof jQuery)throw new Error("Bootstrap's JavaScript requires jQuery");+function(a){"use strict";var b=a.fn.jquery.split(" ")[0].split(".");if(b[0]<2&&b[1]<9||1==b[0]&&9==b[1]&&b[2]<1||b[0]>3)throw new Error("Bootstrap's JavaScript requires jQuery version 1.9.1 or higher, but lower than version 4")}(jQuery),+function(a){"use strict";function b(){var a=document.createElement("bootstrap"),b={WebkitTransition:"webkitTransitionEnd",MozTransition:"transitionend",OTransition:"oTransitionEnd otransitionend",transition:"transitionend"};for(var c in b)if(void 0!==a.style[c])return{end:b[c]};return!1}a.fn.emulateTransitionEnd=function(b){var c=!1,d=this;a(this).one("bsTransitionEnd",function(){c=!0});var e=function(){c||a(d).trigger(a.support.transition.end)};return setTimeout(e,b),this},a(function(){a.support.transition=b(),a.support.transition&&(a.event.special.bsTransitionEnd={bindType:a.support.transition.end,delegateType:a.support.transition.end,handle:function(b){if(a(b.target).is(this))return b.handleObj.handler.apply(this,arguments)}})})}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var c=a(this),e=c.data("bs.alert");e||c.data("bs.alert",e=new d(this)),"string"==typeof b&&e[b].call(c)})}var c='[data-dismiss="alert"]',d=function(b){a(b).on("click",c,this.close)};d.VERSION="3.3.7",d.TRANSITION_DURATION=150,d.prototype.close=function(b){function c(){g.detach().trigger("closed.bs.alert").remove()}var e=a(this),f=e.attr("data-target");f||(f=e.attr("href"),f=f&&f.replace(/.*(?=#[^\s]*$)/,""));var g=a("#"===f?[]:f);b&&b.preventDefault(),g.length||(g=e.closest(".alert")),g.trigger(b=a.Event("close.bs.alert")),b.isDefaultPrevented()||(g.removeClass("in"),a.support.transition&&g.hasClass("fade")?g.one("bsTransitionEnd",c).emulateTransitionEnd(d.TRANSITION_DURATION):c())};var e=a.fn.alert;a.fn.alert=b,a.fn.alert.Constructor=d,a.fn.alert.noConflict=function(){return a.fn.alert=e,this},a(document).on("click.bs.alert.data-api",c,d.prototype.close)}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var d=a(this),e=d.data("bs.button"),f="object"==typeof b&&b;e||d.data("bs.button",e=new c(this,f)),"toggle"==b?e.toggle():b&&e.setState(b)})}var c=function(b,d){this.$element=a(b),this.options=a.extend({},c.DEFAULTS,d),this.isLoading=!1};c.VERSION="3.3.7",c.DEFAULTS={loadingText:"loading..."},c.prototype.setState=function(b){var c="disabled",d=this.$element,e=d.is("input")?"val":"html",f=d.data();b+="Text",null==f.resetText&&d.data("resetText",d[e]()),setTimeout(a.proxy(function(){d[e](null==f[b]?this.options[b]:f[b]),"loadingText"==b?(this.isLoading=!0,d.addClass(c).attr(c,c).prop(c,!0)):this.isLoading&&(this.isLoading=!1,d.removeClass(c).removeAttr(c).prop(c,!1))},this),0)},c.prototype.toggle=function(){var a=!0,b=this.$element.closest('[data-toggle="buttons"]');if(b.length){var c=this.$element.find("input");"radio"==c.prop("type")?(c.prop("checked")&&(a=!1),b.find(".active").removeClass("active"),this.$element.addClass("active")):"checkbox"==c.prop("type")&&(c.prop("checked")!==this.$element.hasClass("active")&&(a=!1),this.$element.toggleClass("active")),c.prop("checked",this.$element.hasClass("active")),a&&c.trigger("change")}else this.$element.attr("aria-pressed",!this.$element.hasClass("active")),this.$element.toggleClass("active")};var d=a.fn.button;a.fn.button=b,a.fn.button.Constructor=c,a.fn.button.noConflict=function(){return a.fn.button=d,this},a(document).on("click.bs.button.data-api",'[data-toggle^="button"]',function(c){var d=a(c.target).closest(".btn");b.call(d,"toggle"),a(c.target).is('input[type="radio"], input[type="checkbox"]')||(c.preventDefault(),d.is("input,button")?d.trigger("focus"):d.find("input:visible,button:visible").first().trigger("focus"))}).on("focus.bs.button.data-api blur.bs.button.data-api",'[data-toggle^="button"]',function(b){a(b.target).closest(".btn").toggleClass("focus",/^focus(in)?$/.test(b.type))})}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var d=a(this),e=d.data("bs.carousel"),f=a.extend({},c.DEFAULTS,d.data(),"object"==typeof b&&b),g="string"==typeof b?b:f.slide;e||d.data("bs.carousel",e=new c(this,f)),"number"==typeof b?e.to(b):g?e[g]():f.interval&&e.pause().cycle()})}var c=function(b,c){this.$element=a(b),this.$indicators=this.$element.find(".carousel-indicators"),this.options=c,this.paused=null,this.sliding=null,this.interval=null,this.$active=null,this.$items=null,this.options.keyboard&&this.$element.on("keydown.bs.carousel",a.proxy(this.keydown,this)),"hover"==this.options.pause&&!("ontouchstart"in document.documentElement)&&this.$element.on("mouseenter.bs.carousel",a.proxy(this.pause,this)).on("mouseleave.bs.carousel",a.proxy(this.cycle,this))};c.VERSION="3.3.7",c.TRANSITION_DURATION=600,c.DEFAULTS={interval:5e3,pause:"hover",wrap:!0,keyboard:!0},c.prototype.keydown=function(a){if(!/input|textarea/i.test(a.target.tagName)){switch(a.which){case 37:this.prev();break;case 39:this.next();break;default:return}a.preventDefault()}},c.prototype.cycle=function(b){return b||(this.paused=!1),this.interval&&clearInterval(this.interval),this.options.interval&&!this.paused&&(this.interval=setInterval(a.proxy(this.next,this),this.options.interval)),this},c.prototype.getItemIndex=function(a){return this.$items=a.parent().children(".item"),this.$items.index(a||this.$active)},c.prototype.getItemForDirection=function(a,b){var c=this.getItemIndex(b),d="prev"==a&&0===c||"next"==a&&c==this.$items.length-1;if(d&&!this.options.wrap)return b;var e="prev"==a?-1:1,f=(c+e)%this.$items.length;return this.$items.eq(f)},c.prototype.to=function(a){var b=this,c=this.getItemIndex(this.$active=this.$element.find(".item.active"));if(!(a>this.$items.length-1||a<0))return this.sliding?this.$element.one("slid.bs.carousel",function(){b.to(a)}):c==a?this.pause().cycle():this.slide(a>c?"next":"prev",this.$items.eq(a))},c.prototype.pause=function(b){return b||(this.paused=!0),this.$element.find(".next, .prev").length&&a.support.transition&&(this.$element.trigger(a.support.transition.end),this.cycle(!0)),this.interval=clearInterval(this.interval),this},c.prototype.next=function(){if(!this.sliding)return this.slide("next")},c.prototype.prev=function(){if(!this.sliding)return this.slide("prev")},c.prototype.slide=function(b,d){var e=this.$element.find(".item.active"),f=d||this.getItemForDirection(b,e),g=this.interval,h="next"==b?"left":"right",i=this;if(f.hasClass("active"))return this.sliding=!1;var j=f[0],k=a.Event("slide.bs.carousel",{relatedTarget:j,direction:h});if(this.$element.trigger(k),!k.isDefaultPrevented()){if(this.sliding=!0,g&&this.pause(),this.$indicators.length){this.$indicators.find(".active").removeClass("active");var l=a(this.$indicators.children()[this.getItemIndex(f)]);l&&l.addClass("active")}var m=a.Event("slid.bs.carousel",{relatedTarget:j,direction:h});return a.support.transition&&this.$element.hasClass("slide")?(f.addClass(b),f[0].offsetWidth,e.addClass(h),f.addClass(h),e.one("bsTransitionEnd",function(){f.removeClass([b,h].join(" ")).addClass("active"),e.removeClass(["active",h].join(" ")),i.sliding=!1,setTimeout(function(){i.$element.trigger(m)},0)}).emulateTransitionEnd(c.TRANSITION_DURATION)):(e.removeClass("active"),f.addClass("active"),this.sliding=!1,this.$element.trigger(m)),g&&this.cycle(),this}};var d=a.fn.carousel;a.fn.carousel=b,a.fn.carousel.Constructor=c,a.fn.carousel.noConflict=function(){return a.fn.carousel=d,this};var e=function(c){var d,e=a(this),f=a(e.attr("data-target")||(d=e.attr("href"))&&d.replace(/.*(?=#[^\s]+$)/,""));if(f.hasClass("carousel")){var g=a.extend({},f.data(),e.data()),h=e.attr("data-slide-to");h&&(g.interval=!1),b.call(f,g),h&&f.data("bs.carousel").to(h),c.preventDefault()}};a(document).on("click.bs.carousel.data-api","[data-slide]",e).on("click.bs.carousel.data-api","[data-slide-to]",e),a(window).on("load",function(){a('[data-ride="carousel"]').each(function(){var c=a(this);b.call(c,c.data())})})}(jQuery),+function(a){"use strict";function b(b){var c,d=b.attr("data-target")||(c=b.attr("href"))&&c.replace(/.*(?=#[^\s]+$)/,"");return a(d)}function c(b){return this.each(function(){var c=a(this),e=c.data("bs.collapse"),f=a.extend({},d.DEFAULTS,c.data(),"object"==typeof b&&b);!e&&f.toggle&&/show|hide/.test(b)&&(f.toggle=!1),e||c.data("bs.collapse",e=new d(this,f)),"string"==typeof b&&e[b]()})}var d=function(b,c){this.$element=a(b),this.options=a.extend({},d.DEFAULTS,c),this.$trigger=a('[data-toggle="collapse"][href="#'+b.id+'"],[data-toggle="collapse"][data-target="#'+b.id+'"]'),this.transitioning=null,this.options.parent?this.$parent=this.getParent():this.addAriaAndCollapsedClass(this.$element,this.$trigger),this.options.toggle&&this.toggle()};d.VERSION="3.3.7",d.TRANSITION_DURATION=350,d.DEFAULTS={toggle:!0},d.prototype.dimension=function(){var a=this.$element.hasClass("width");return a?"width":"height"},d.prototype.show=function(){if(!this.transitioning&&!this.$element.hasClass("in")){var b,e=this.$parent&&this.$parent.children(".panel").children(".in, .collapsing");if(!(e&&e.length&&(b=e.data("bs.collapse"),b&&b.transitioning))){var f=a.Event("show.bs.collapse");if(this.$element.trigger(f),!f.isDefaultPrevented()){e&&e.length&&(c.call(e,"hide"),b||e.data("bs.collapse",null));var g=this.dimension();this.$element.removeClass("collapse").addClass("collapsing")[g](0).attr("aria-expanded",!0),this.$trigger.removeClass("collapsed").attr("aria-expanded",!0),this.transitioning=1;var h=function(){this.$element.removeClass("collapsing").addClass("collapse in")[g](""),this.transitioning=0,this.$element.trigger("shown.bs.collapse")};if(!a.support.transition)return h.call(this);var i=a.camelCase(["scroll",g].join("-"));this.$element.one("bsTransitionEnd",a.proxy(h,this)).emulateTransitionEnd(d.TRANSITION_DURATION)[g](this.$element[0][i])}}}},d.prototype.hide=function(){if(!this.transitioning&&this.$element.hasClass("in")){var b=a.Event("hide.bs.collapse");if(this.$element.trigger(b),!b.isDefaultPrevented()){var c=this.dimension();this.$element[c](this.$element[c]())[0].offsetHeight,this.$element.addClass("collapsing").removeClass("collapse in").attr("aria-expanded",!1),this.$trigger.addClass("collapsed").attr("aria-expanded",!1),this.transitioning=1;var e=function(){this.transitioning=0,this.$element.removeClass("collapsing").addClass("collapse").trigger("hidden.bs.collapse")};return a.support.transition?void this.$element[c](0).one("bsTransitionEnd",a.proxy(e,this)).emulateTransitionEnd(d.TRANSITION_DURATION):e.call(this)}}},d.prototype.toggle=function(){this[this.$element.hasClass("in")?"hide":"show"]()},d.prototype.getParent=function(){return a(this.options.parent).find('[data-toggle="collapse"][data-parent="'+this.options.parent+'"]').each(a.proxy(function(c,d){var e=a(d);this.addAriaAndCollapsedClass(b(e),e)},this)).end()},d.prototype.addAriaAndCollapsedClass=function(a,b){var c=a.hasClass("in");a.attr("aria-expanded",c),b.toggleClass("collapsed",!c).attr("aria-expanded",c)};var e=a.fn.collapse;a.fn.collapse=c,a.fn.collapse.Constructor=d,a.fn.collapse.noConflict=function(){return a.fn.collapse=e,this},a(document).on("click.bs.collapse.data-api",'[data-toggle="collapse"]',function(d){var e=a(this);e.attr("data-target")||d.preventDefault();var f=b(e),g=f.data("bs.collapse"),h=g?"toggle":e.data();c.call(f,h)})}(jQuery),+function(a){"use strict";function b(b){var c=b.attr("data-target");c||(c=b.attr("href"),c=c&&/#[A-Za-z]/.test(c)&&c.replace(/.*(?=#[^\s]*$)/,""));var d=c&&a(c);return d&&d.length?d:b.parent()}function c(c){c&&3===c.which||(a(e).remove(),a(f).each(function(){var d=a(this),e=b(d),f={relatedTarget:this};e.hasClass("open")&&(c&&"click"==c.type&&/input|textarea/i.test(c.target.tagName)&&a.contains(e[0],c.target)||(e.trigger(c=a.Event("hide.bs.dropdown",f)),c.isDefaultPrevented()||(d.attr("aria-expanded","false"),e.removeClass("open").trigger(a.Event("hidden.bs.dropdown",f)))))}))}function d(b){return this.each(function(){var c=a(this),d=c.data("bs.dropdown");d||c.data("bs.dropdown",d=new g(this)),"string"==typeof b&&d[b].call(c)})}var e=".dropdown-backdrop",f='[data-toggle="dropdown"]',g=function(b){a(b).on("click.bs.dropdown",this.toggle)};g.VERSION="3.3.7",g.prototype.toggle=function(d){var e=a(this);if(!e.is(".disabled, :disabled")){var f=b(e),g=f.hasClass("open");if(c(),!g){"ontouchstart"in document.documentElement&&!f.closest(".navbar-nav").length&&a(document.createElement("div")).addClass("dropdown-backdrop").insertAfter(a(this)).on("click",c);var h={relatedTarget:this};if(f.trigger(d=a.Event("show.bs.dropdown",h)),d.isDefaultPrevented())return;e.trigger("focus").attr("aria-expanded","true"),f.toggleClass("open").trigger(a.Event("shown.bs.dropdown",h))}return!1}},g.prototype.keydown=function(c){if(/(38|40|27|32)/.test(c.which)&&!/input|textarea/i.test(c.target.tagName)){var d=a(this);if(c.preventDefault(),c.stopPropagation(),!d.is(".disabled, :disabled")){var e=b(d),g=e.hasClass("open");if(!g&&27!=c.which||g&&27==c.which)return 27==c.which&&e.find(f).trigger("focus"),d.trigger("click");var h=" li:not(.disabled):visible a",i=e.find(".dropdown-menu"+h);if(i.length){var j=i.index(c.target);38==c.which&&j>0&&j--,40==c.which&&jdocument.documentElement.clientHeight;this.$element.css({paddingLeft:!this.bodyIsOverflowing&&a?this.scrollbarWidth:"",paddingRight:this.bodyIsOverflowing&&!a?this.scrollbarWidth:""})},c.prototype.resetAdjustments=function(){this.$element.css({paddingLeft:"",paddingRight:""})},c.prototype.checkScrollbar=function(){var a=window.innerWidth;if(!a){var b=document.documentElement.getBoundingClientRect();a=b.right-Math.abs(b.left)}this.bodyIsOverflowing=document.body.clientWidth
',trigger:"hover focus",title:"",delay:0,html:!1,container:!1,viewport:{selector:"body",padding:0}},c.prototype.init=function(b,c,d){if(this.enabled=!0,this.type=b,this.$element=a(c),this.options=this.getOptions(d),this.$viewport=this.options.viewport&&a(a.isFunction(this.options.viewport)?this.options.viewport.call(this,this.$element):this.options.viewport.selector||this.options.viewport),this.inState={click:!1,hover:!1,focus:!1},this.$element[0]instanceof document.constructor&&!this.options.selector)throw new Error("`selector` option must be specified when initializing "+this.type+" on the window.document object!");for(var e=this.options.trigger.split(" "),f=e.length;f--;){var g=e[f];if("click"==g)this.$element.on("click."+this.type,this.options.selector,a.proxy(this.toggle,this));else if("manual"!=g){var h="hover"==g?"mouseenter":"focusin",i="hover"==g?"mouseleave":"focusout";this.$element.on(h+"."+this.type,this.options.selector,a.proxy(this.enter,this)),this.$element.on(i+"."+this.type,this.options.selector,a.proxy(this.leave,this))}}this.options.selector?this._options=a.extend({},this.options,{trigger:"manual",selector:""}):this.fixTitle()},c.prototype.getDefaults=function(){return c.DEFAULTS},c.prototype.getOptions=function(b){return b=a.extend({},this.getDefaults(),this.$element.data(),b),b.delay&&"number"==typeof b.delay&&(b.delay={show:b.delay,hide:b.delay}),b},c.prototype.getDelegateOptions=function(){var b={},c=this.getDefaults();return this._options&&a.each(this._options,function(a,d){c[a]!=d&&(b[a]=d)}),b},c.prototype.enter=function(b){var c=b instanceof this.constructor?b:a(b.currentTarget).data("bs."+this.type);return c||(c=new this.constructor(b.currentTarget,this.getDelegateOptions()),a(b.currentTarget).data("bs."+this.type,c)),b instanceof a.Event&&(c.inState["focusin"==b.type?"focus":"hover"]=!0),c.tip().hasClass("in")||"in"==c.hoverState?void(c.hoverState="in"):(clearTimeout(c.timeout),c.hoverState="in",c.options.delay&&c.options.delay.show?void(c.timeout=setTimeout(function(){"in"==c.hoverState&&c.show()},c.options.delay.show)):c.show())},c.prototype.isInStateTrue=function(){for(var a in this.inState)if(this.inState[a])return!0;return!1},c.prototype.leave=function(b){var c=b instanceof this.constructor?b:a(b.currentTarget).data("bs."+this.type);if(c||(c=new this.constructor(b.currentTarget,this.getDelegateOptions()),a(b.currentTarget).data("bs."+this.type,c)),b instanceof a.Event&&(c.inState["focusout"==b.type?"focus":"hover"]=!1),!c.isInStateTrue())return clearTimeout(c.timeout),c.hoverState="out",c.options.delay&&c.options.delay.hide?void(c.timeout=setTimeout(function(){"out"==c.hoverState&&c.hide()},c.options.delay.hide)):c.hide()},c.prototype.show=function(){var b=a.Event("show.bs."+this.type);if(this.hasContent()&&this.enabled){this.$element.trigger(b);var d=a.contains(this.$element[0].ownerDocument.documentElement,this.$element[0]);if(b.isDefaultPrevented()||!d)return;var e=this,f=this.tip(),g=this.getUID(this.type);this.setContent(),f.attr("id",g),this.$element.attr("aria-describedby",g),this.options.animation&&f.addClass("fade");var h="function"==typeof this.options.placement?this.options.placement.call(this,f[0],this.$element[0]):this.options.placement,i=/\s?auto?\s?/i,j=i.test(h);j&&(h=h.replace(i,"")||"top"),f.detach().css({top:0,left:0,display:"block"}).addClass(h).data("bs."+this.type,this),this.options.container?f.appendTo(this.options.container):f.insertAfter(this.$element),this.$element.trigger("inserted.bs."+this.type);var k=this.getPosition(),l=f[0].offsetWidth,m=f[0].offsetHeight;if(j){var n=h,o=this.getPosition(this.$viewport);h="bottom"==h&&k.bottom+m>o.bottom?"top":"top"==h&&k.top-mo.width?"left":"left"==h&&k.left-lg.top+g.height&&(e.top=g.top+g.height-i)}else{var j=b.left-f,k=b.left+f+c;jg.right&&(e.left=g.left+g.width-k)}return e},c.prototype.getTitle=function(){var a,b=this.$element,c=this.options;return a=b.attr("data-original-title")||("function"==typeof c.title?c.title.call(b[0]):c.title)},c.prototype.getUID=function(a){do a+=~~(1e6*Math.random());while(document.getElementById(a));return a},c.prototype.tip=function(){if(!this.$tip&&(this.$tip=a(this.options.template),1!=this.$tip.length))throw new Error(this.type+" `template` option must consist of exactly 1 top-level element!");return this.$tip},c.prototype.arrow=function(){return this.$arrow=this.$arrow||this.tip().find(".tooltip-arrow")},c.prototype.enable=function(){this.enabled=!0},c.prototype.disable=function(){this.enabled=!1},c.prototype.toggleEnabled=function(){this.enabled=!this.enabled},c.prototype.toggle=function(b){var c=this;b&&(c=a(b.currentTarget).data("bs."+this.type),c||(c=new this.constructor(b.currentTarget,this.getDelegateOptions()),a(b.currentTarget).data("bs."+this.type,c))),b?(c.inState.click=!c.inState.click,c.isInStateTrue()?c.enter(c):c.leave(c)):c.tip().hasClass("in")?c.leave(c):c.enter(c)},c.prototype.destroy=function(){var a=this;clearTimeout(this.timeout),this.hide(function(){a.$element.off("."+a.type).removeData("bs."+a.type),a.$tip&&a.$tip.detach(),a.$tip=null,a.$arrow=null,a.$viewport=null,a.$element=null})};var d=a.fn.tooltip;a.fn.tooltip=b,a.fn.tooltip.Constructor=c,a.fn.tooltip.noConflict=function(){return a.fn.tooltip=d,this}}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var d=a(this),e=d.data("bs.popover"),f="object"==typeof b&&b;!e&&/destroy|hide/.test(b)||(e||d.data("bs.popover",e=new c(this,f)),"string"==typeof b&&e[b]())})}var c=function(a,b){this.init("popover",a,b)};if(!a.fn.tooltip)throw new Error("Popover requires tooltip.js");c.VERSION="3.3.7",c.DEFAULTS=a.extend({},a.fn.tooltip.Constructor.DEFAULTS,{placement:"right",trigger:"click",content:"",template:''}),c.prototype=a.extend({},a.fn.tooltip.Constructor.prototype),c.prototype.constructor=c,c.prototype.getDefaults=function(){return c.DEFAULTS},c.prototype.setContent=function(){var a=this.tip(),b=this.getTitle(),c=this.getContent();a.find(".popover-title")[this.options.html?"html":"text"](b),a.find(".popover-content").children().detach().end()[this.options.html?"string"==typeof c?"html":"append":"text"](c),a.removeClass("fade top bottom left right in"),a.find(".popover-title").html()||a.find(".popover-title").hide()},c.prototype.hasContent=function(){return this.getTitle()||this.getContent()},c.prototype.getContent=function(){var a=this.$element,b=this.options;return a.attr("data-content")||("function"==typeof b.content?b.content.call(a[0]):b.content)},c.prototype.arrow=function(){return this.$arrow=this.$arrow||this.tip().find(".arrow")};var d=a.fn.popover;a.fn.popover=b,a.fn.popover.Constructor=c,a.fn.popover.noConflict=function(){return a.fn.popover=d,this}}(jQuery),+function(a){"use strict";function b(c,d){this.$body=a(document.body),this.$scrollElement=a(a(c).is(document.body)?window:c),this.options=a.extend({},b.DEFAULTS,d),this.selector=(this.options.target||"")+" .nav li > a",this.offsets=[],this.targets=[],this.activeTarget=null,this.scrollHeight=0,this.$scrollElement.on("scroll.bs.scrollspy",a.proxy(this.process,this)),this.refresh(),this.process()}function c(c){return this.each(function(){var d=a(this),e=d.data("bs.scrollspy"),f="object"==typeof c&&c;e||d.data("bs.scrollspy",e=new b(this,f)),"string"==typeof c&&e[c]()})}b.VERSION="3.3.7",b.DEFAULTS={offset:10},b.prototype.getScrollHeight=function(){return this.$scrollElement[0].scrollHeight||Math.max(this.$body[0].scrollHeight,document.documentElement.scrollHeight)},b.prototype.refresh=function(){var b=this,c="offset",d=0;this.offsets=[],this.targets=[],this.scrollHeight=this.getScrollHeight(),a.isWindow(this.$scrollElement[0])||(c="position",d=this.$scrollElement.scrollTop()),this.$body.find(this.selector).map(function(){var b=a(this),e=b.data("target")||b.attr("href"),f=/^#./.test(e)&&a(e);return f&&f.length&&f.is(":visible")&&[[f[c]().top+d,e]]||null}).sort(function(a,b){return a[0]-b[0]}).each(function(){b.offsets.push(this[0]),b.targets.push(this[1])})},b.prototype.process=function(){var a,b=this.$scrollElement.scrollTop()+this.options.offset,c=this.getScrollHeight(),d=this.options.offset+c-this.$scrollElement.height(),e=this.offsets,f=this.targets,g=this.activeTarget;if(this.scrollHeight!=c&&this.refresh(),b>=d)return g!=(a=f[f.length-1])&&this.activate(a);if(g&&b=e[a]&&(void 0===e[a+1]||b .dropdown-menu > .active").removeClass("active").end().find('[data-toggle="tab"]').attr("aria-expanded",!1),b.addClass("active").find('[data-toggle="tab"]').attr("aria-expanded",!0),h?(b[0].offsetWidth,b.addClass("in")):b.removeClass("fade"),b.parent(".dropdown-menu").length&&b.closest("li.dropdown").addClass("active").end().find('[data-toggle="tab"]').attr("aria-expanded",!0),e&&e()}var g=d.find("> .active"),h=e&&a.support.transition&&(g.length&&g.hasClass("fade")||!!d.find("> .fade").length);g.length&&h?g.one("bsTransitionEnd",f).emulateTransitionEnd(c.TRANSITION_DURATION):f(),g.removeClass("in")};var d=a.fn.tab;a.fn.tab=b,a.fn.tab.Constructor=c,a.fn.tab.noConflict=function(){return a.fn.tab=d,this};var e=function(c){c.preventDefault(),b.call(a(this),"show")};a(document).on("click.bs.tab.data-api",'[data-toggle="tab"]',e).on("click.bs.tab.data-api",'[data-toggle="pill"]',e)}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var d=a(this),e=d.data("bs.affix"),f="object"==typeof b&&b;e||d.data("bs.affix",e=new c(this,f)),"string"==typeof b&&e[b]()})}var c=function(b,d){this.options=a.extend({},c.DEFAULTS,d),this.$target=a(this.options.target).on("scroll.bs.affix.data-api",a.proxy(this.checkPosition,this)).on("click.bs.affix.data-api",a.proxy(this.checkPositionWithEventLoop,this)),this.$element=a(b),this.affixed=null,this.unpin=null,this.pinnedOffset=null,this.checkPosition()};c.VERSION="3.3.7",c.RESET="affix affix-top affix-bottom",c.DEFAULTS={offset:0,target:window},c.prototype.getState=function(a,b,c,d){var e=this.$target.scrollTop(),f=this.$element.offset(),g=this.$target.height();if(null!=c&&"top"==this.affixed)return e=a-d&&"bottom"},c.prototype.getPinnedOffset=function(){if(this.pinnedOffset)return this.pinnedOffset;this.$element.removeClass(c.RESET).addClass("affix");var a=this.$target.scrollTop(),b=this.$element.offset();return this.pinnedOffset=b.top-a},c.prototype.checkPositionWithEventLoop=function(){setTimeout(a.proxy(this.checkPosition,this),1)},c.prototype.checkPosition=function(){if(this.$element.is(":visible")){var b=this.$element.height(),d=this.options.offset,e=d.top,f=d.bottom,g=Math.max(a(document).height(),a(document.body).height());"object"!=typeof d&&(f=e=d),"function"==typeof e&&(e=d.top(this.$element)),"function"==typeof f&&(f=d.bottom(this.$element));var h=this.getState(g,b,e,f);if(this.affixed!=h){null!=this.unpin&&this.$element.css("top","");var i="affix"+(h?"-"+h:""),j=a.Event(i+".bs.affix");if(this.$element.trigger(j),j.isDefaultPrevented())return;this.affixed=h,this.unpin="bottom"==h?this.getPinnedOffset():null,this.$element.removeClass(c.RESET).addClass(i).trigger(i.replace("affix","affixed")+".bs.affix")}"bottom"==h&&this.$element.offset({top:g-b-f})}};var d=a.fn.affix;a.fn.affix=b,a.fn.affix.Constructor=c,a.fn.affix.noConflict=function(){return a.fn.affix=d,this},a(window).on("load",function(){a('[data-spy="affix"]').each(function(){var c=a(this),d=c.data();d.offset=d.offset||{},null!=d.offsetBottom&&(d.offset.bottom=d.offsetBottom),null!=d.offsetTop&&(d.offset.top=d.offsetTop),b.call(c,d)})})}(jQuery); -------------------------------------------------------------------------------- /mysite/static/home.css: -------------------------------------------------------------------------------- 1 | h3.home-content { 2 | font-size: 2em; 3 | text-align: center; 4 | margin-top: 4em; 5 | margin-bottom: 2em; 6 | } 7 | 8 | div#container { 9 | margin: 0 auto; 10 | height:200px; 11 | width: 600px; 12 | } 13 | 14 | div.hot-data { 15 | text-align: center; 16 | margin-top: 2em 17 | } -------------------------------------------------------------------------------- /mysite/templates/base.html: -------------------------------------------------------------------------------- 1 | {% load staticfiles %} 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | {% block title %}{% endblock title %} 10 | 11 | 12 | 13 | 14 | {% block header_extends %} 15 | {% endblock header_extends %} 16 | 17 | 18 | 19 | 64 | 65 | 66 | 67 | 68 | {% block content %}{% endblock content %} 69 | 70 | 93 | 94 | 112 | {% block script_extends %}{% endblock script_extends %} 113 | 114 | -------------------------------------------------------------------------------- /mysite/templates/error.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | {% load staticfiles %} 3 | 4 | {% block nav_home_active %}active{% endblock %} 5 | 6 | {% block title %} 7 | 我的网站|错误 8 | {% endblock title %} 9 | {% block header_extends %} 10 | 11 | 12 | {% endblock header_extends %} 13 | 14 | {% block content %} 15 | {{ message }},返回 16 | {% endblock content %} -------------------------------------------------------------------------------- /mysite/templates/form.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | {% load staticfiles %} 3 | 4 | {% block nav_home_active %}active{% endblock %} 5 | 6 | {% block title %} {{ page_title }} {% endblock title %} 7 | {% block header_extends %} 8 | 9 | 10 | {% endblock header_extends %} 11 | 12 | {% block content %} 13 |
14 |
15 |
16 | 17 | 18 |
19 |
20 |

{{ form_title }}

21 |
22 |
23 |
24 | {% csrf_token %} 25 | {% for field in form %} 26 | {% if not field.is_hidden %} 27 | 28 | {% endif %} 29 | {{ field }} 30 |

31 | {{ field.errors.as_text }} 32 |

33 | {% endfor %} 34 | {{ form.non_field_errors }} 35 |
36 |
37 | {% block other_buttons %} {% endblock other_buttons %} 38 |
39 |
40 | 41 | 42 |
43 |
44 |
45 |
46 | 47 | 48 |
49 |
50 |
51 | {% endblock content %} -------------------------------------------------------------------------------- /mysite/templates/home.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | {% load staticfiles %} 3 | 4 | {% block nav_home_active %}active{% endblock %} 5 | 6 | {% block title %} 7 | 我的网站|首页 8 | {% endblock title %} 9 | {% block header_extends %} 10 | 11 | 12 | {% endblock header_extends %} 13 | 14 | {% block content %} 15 |

欢迎访问我的网站,随便看看

16 | 17 |
18 |
19 |

今天热门点击

20 |
    21 | {% for hot_data in today_hot_data %} 22 |
  • {{ hot_data.content_object.title }}({{ hot_data.read_num }})
  • 23 | {% empty %} 24 |
  • 今天暂时没有热门博客
  • 25 | {% endfor %} 26 |
27 |
28 |
29 |

昨天热门点击

30 |
    31 | {% for hot_data in yesterday_hot_data %} 32 |
  • {{ hot_data.content_object.title }}({{ hot_data.read_num }})
  • 33 | {% empty %} 34 |
  • 昨天没有热门博客
  • 35 | {% endfor %} 36 |
37 |
38 |
39 |

7天热门博客

40 |
    41 | {% for hot_data in hot_data_for_7_days %} 42 |
  • {{ hot_data.title }}({{ hot_data.read_num_sum }})
  • 43 | {% empty %} 44 |
  • 没有热门博客
  • 45 | {% endfor %} 46 |
47 |
48 | 49 | 87 | {% endblock content %} -------------------------------------------------------------------------------- /mysite/user/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/able8/Django-Course/8cc6cabe7789117f37e009db842b8f8e41d8e5a5/mysite/user/__init__.py -------------------------------------------------------------------------------- /mysite/user/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | from django.contrib.auth.admin import UserAdmin as BaseUserAdmin 3 | from django.contrib.auth.models import User 4 | from .models import Profile 5 | 6 | 7 | # Define an inline admin descriptor for Profile model 8 | class ProfileInline(admin.StackedInline): 9 | model = Profile 10 | can_delete = False 11 | 12 | # Define a new User admin 13 | class UserAdmin(BaseUserAdmin): 14 | inlines = (ProfileInline,) 15 | list_display = ('username', 'nickname', 'email', 'is_staff', 'is_active', 'is_superuser') 16 | # 为了在用户列表显示昵称,需要加入一个自定义方法。上面就是调用user.nickname显示 17 | def nickname(self, obj): 18 | return obj.profile.nickname 19 | nickname.short_description = '昵称' # 中文显示 20 | 21 | # Re-register UserAdmin 22 | admin.site.unregister(User) 23 | admin.site.register(User, UserAdmin) 24 | 25 | 26 | @admin.register(Profile) 27 | class ProfileAdmin(admin.ModelAdmin): 28 | list_display = ('user', 'nickname') -------------------------------------------------------------------------------- /mysite/user/context_processors.py: -------------------------------------------------------------------------------- 1 | from .forms import LoginForm 2 | 3 | 4 | def login_modal_form(request): 5 | return {'login_modal_form': LoginForm()} -------------------------------------------------------------------------------- /mysite/user/forms.py: -------------------------------------------------------------------------------- 1 | from django import forms 2 | from django.contrib import auth 3 | from django.contrib.auth.models import User 4 | 5 | 6 | # 定制登录表单 7 | class LoginForm(forms.Form): 8 | username_or_email = forms.CharField( 9 | label='用户名或邮箱', 10 | required=True, # 默认为True 11 | widget=forms.TextInput(attrs={ 12 | 'class': 'form-control', 13 | 'placeholder': '请输入用户名或邮箱' 14 | })) 15 | # 设置渲染后的html的属性 16 | 17 | password = forms.CharField( 18 | label='密码', 19 | widget=forms.PasswordInput(attrs={ 20 | 'class': 'form-control', 21 | 'placeholder': '请输入密码' 22 | })) 23 | 24 | # 验证数据方法 25 | def clean(self): 26 | username_or_email = self.cleaned_data['username_or_email'] 27 | password = self.cleaned_data['password'] 28 | user = auth.authenticate(username=username_or_email, password=password) 29 | if user is None: 30 | if User.objects.filter(email=username_or_email).exists(): 31 | username = User.objects.get(email=username_or_email).username 32 | user = auth.authenticate(username=username, password=password) 33 | if user is not None: 34 | self.cleaned_data['user'] = user 35 | return self.cleaned_data 36 | raise forms.ValidationError('用户名或密码错误') 37 | else: 38 | self.cleaned_data['user'] = user 39 | return self.cleaned_data 40 | 41 | 42 | class RegForm(forms.Form): 43 | username = forms.CharField( 44 | label='用户名', 45 | required=True, # 默认为True 46 | max_length=30, 47 | min_length=4, 48 | widget=forms.TextInput(attrs={ 49 | 'class': 'form-control', 50 | 'placeholder': '请输入3-30位用户名' 51 | })) 52 | password = forms.CharField( 53 | label='设置密码', 54 | min_length=6, 55 | widget=forms.PasswordInput(attrs={ 56 | 'class': 'form-control', 57 | 'placeholder': '请输入密码' 58 | })) 59 | password_again = forms.CharField( 60 | label='确认密码', 61 | min_length=6, 62 | widget=forms.PasswordInput(attrs={ 63 | 'class': 'form-control', 64 | 'placeholder': '再输入一次密码' 65 | })) 66 | email = forms.EmailField( 67 | label='邮箱', 68 | widget=forms.TextInput(attrs={ 69 | 'class': 'form-control', 70 | 'placeholder': '请输入邮箱' 71 | })) 72 | verification_code = forms.CharField( 73 | label='验证码', 74 | required=False, # 为了在不填的时候可以点击发送邮件 75 | widget=forms.TextInput(attrs={ 76 | 'class': 'form-control', 77 | 'placeholder': '点击“发送验证码”发送到邮箱' 78 | })) 79 | 80 | def __init__(self, *args, **kwargs): 81 | if 'request' in kwargs: 82 | self.request = kwargs.pop('request') # 接收传入的rquest信息, 并剔除,为了下一句不出错 83 | super(RegForm, self).__init__(*args, **kwargs) 84 | 85 | # 验证数据 86 | def clean(self): 87 | # 判断验证码 88 | code = self.request.session.get('register_code', '') 89 | verification_code = self.cleaned_data.get('verification_code', '') 90 | if not (code != '' and code == verification_code): 91 | raise forms.ValidationError('验证码不正确') 92 | return self.cleaned_data 93 | 94 | # 验证数据, 是否有效,是否存在 95 | def clean_username(self): 96 | username = self.cleaned_data['username'] 97 | if User.objects.filter(username=username).exists(): 98 | raise forms.ValidationError('用户名已存在') 99 | return username 100 | 101 | def clean_email(self): 102 | email = self.cleaned_data['email'] 103 | if User.objects.filter(email=email).exists(): 104 | raise forms.ValidationError('邮箱已存在') 105 | return email 106 | 107 | def clean_password_again(self): 108 | password = self.cleaned_data['password'] 109 | password_again = self.cleaned_data['password_again'] 110 | if password != password_again: 111 | raise forms.ValidationError('两次输入的密码不一致') 112 | return password_again 113 | 114 | 115 | class ChangeNicknameForm(forms.Form): 116 | nickname_new = forms.CharField( 117 | label='新的昵称', 118 | max_length=20, 119 | widget=forms.TextInput(attrs={ 120 | 'class': 'form-control', 121 | 'placeholder': '请输入新的昵称' 122 | })) 123 | 124 | # 下面2个函数用于判断用户是否登录 125 | def __init__(self, *args, **kwargs): 126 | if 'user' in kwargs: 127 | self.user = kwargs.pop('user') # 接收用户信息, 并剔除,为了下一句不出错 128 | super(ChangeNicknameForm, self).__init__(*args, **kwargs) 129 | 130 | # 验证数据 131 | def clean(self): 132 | # 判断用户是否登录 133 | if self.user.is_authenticated: 134 | self.cleaned_data['user'] = self.user 135 | else: 136 | raise forms.ValidationError('用户尚未登录') 137 | return self.cleaned_data 138 | 139 | def clean_nickname_new(self): 140 | nickname_new = self.cleaned_data.get('nickname_new', '').strip() 141 | if nickname_new == '': 142 | raise forms.ValidationError('新的昵称不能为空') 143 | return nickname_new 144 | 145 | 146 | class BindEmailForm(forms.Form): 147 | email = forms.EmailField( 148 | label='邮箱', 149 | widget=forms.TextInput(attrs={ 150 | 'class': 'form-control', 151 | 'placeholder': '请输入正确的邮箱' 152 | })) 153 | verification_code = forms.CharField( 154 | label='验证码', 155 | required=False, # 为了在不填的时候可以点击发送邮件 156 | widget=forms.TextInput(attrs={ 157 | 'class': 'form-control', 158 | 'placeholder': '点击“发送验证码”发送到邮箱' 159 | })) 160 | 161 | # 下面2个函数用于判断用户是否登录 162 | def __init__(self, *args, **kwargs): 163 | if 'request' in kwargs: 164 | self.request = kwargs.pop('request') # 接收传入的rquest信息, 并剔除,为了下一句不出错 165 | super(BindEmailForm, self).__init__(*args, **kwargs) 166 | 167 | # 验证数据 168 | def clean(self): 169 | # 判断用户是否登录 170 | if self.request.user.is_authenticated: 171 | self.cleaned_data['user'] = self.request.user 172 | else: 173 | raise forms.ValidationError('用户尚未登录') 174 | 175 | # 判断用户数会否已经绑定邮箱 176 | if self.request.user.email != '': 177 | raise forms.ValidationError('你已经绑定了邮箱') 178 | 179 | # 判断验证码 180 | code = self.request.session.get('bind_email_code', '') 181 | verification_code = self.cleaned_data.get('verification_code', '') 182 | if not (code != '' and code == verification_code): 183 | raise forms.ValidationError('验证码不正确') 184 | return self.cleaned_data 185 | 186 | def clean_email(self): 187 | email = self.cleaned_data['email'] 188 | if User.objects.filter(email=email).exists(): 189 | raise forms.ValidationError('该邮箱已经被绑定') 190 | return email 191 | 192 | def clean_verification_code(self): 193 | verification_code = self.cleaned_data.get('verification_code', 194 | '').strip() 195 | if verification_code == '': 196 | raise forms.ValidationError('验证码不能为空') 197 | return verification_code 198 | 199 | 200 | class ChangePasswordForm(forms.Form): 201 | old_password = forms.CharField( 202 | label='旧的密码', 203 | widget=forms.PasswordInput(attrs={ 204 | 'class': 'form-control', 205 | 'placeholder': '请输入旧的密码' 206 | })) 207 | new_password = forms.CharField( 208 | label='新的密码', 209 | widget=forms.PasswordInput(attrs={ 210 | 'class': 'form-control', 211 | 'placeholder': '请输入新的密码' 212 | })) 213 | new_password_again = forms.CharField( 214 | label='请再次输入新的密码', 215 | widget=forms.PasswordInput(attrs={ 216 | 'class': 'form-control', 217 | 'placeholder': '请再次输入新的密码' 218 | })) 219 | 220 | def __init__(self, *args, **kwargs): 221 | if 'user' in kwargs: 222 | self.user = kwargs.pop('user') 223 | super(ChangePasswordForm, self).__init__(*args, **kwargs) 224 | 225 | def clean(self): 226 | # 验证新的密码是否一致 227 | new_password = self.cleaned_data.get('new_password', '') 228 | new_password_again = self.cleaned_data.get('new_password_again', '') 229 | if new_password != new_password_again or new_password == '': 230 | raise forms.ValidationError('两次输入的密码不一致') 231 | 232 | def clean_old_password(self): 233 | # 验证旧的密码是否正确 234 | old_password = self.cleaned_data.get('old_password', '') 235 | if not self.user.check_password(old_password): 236 | raise forms.ValidationError('旧的密码错误') 237 | 238 | 239 | class ForgotPasswordForm(forms.Form): 240 | email = forms.EmailField( 241 | label='邮箱', 242 | widget=forms.TextInput(attrs={ 243 | 'class': 'form-control', 244 | 'placeholder': '请输入绑定过的邮箱' 245 | })) 246 | new_password = forms.CharField( 247 | label='新的密码', 248 | min_length=6, 249 | widget=forms.PasswordInput(attrs={ 250 | 'class': 'form-control', 251 | 'placeholder': '请输入新的密码' 252 | })) 253 | verification_code = forms.CharField( 254 | label='验证码', 255 | required=False, # 为了在不填的时候可以点击发送邮件 256 | widget=forms.TextInput(attrs={ 257 | 'class': 'form-control', 258 | 'placeholder': '点击“发送验证码”发送到邮箱' 259 | })) 260 | 261 | def __init__(self, *args, **kwargs): 262 | if 'user' in kwargs: 263 | self.user = kwargs.pop('user') # 接收传入的rquest信息, 并剔除,为了下一句不出错 264 | super(ForgotPasswordForm, self).__init__(*args, **kwargs) 265 | 266 | def clean_email(self): 267 | email = self.cleaned_data['email'].strip() 268 | if not User.objects.filter(email=email).exists(): 269 | raise forms.ValidationError('邮箱不存在') 270 | return email 271 | 272 | def clean_verification_code(self): 273 | verification_code = self.cleaned_data.get('verification_code', 274 | '').strip() 275 | if verification_code == '': 276 | raise forms.ValidationError('验证码不能为空') 277 | return verification_code 278 | 279 | # 判断验证码 280 | code = self.request.session.get('bind_email_code', '') 281 | verification_code = self.cleaned_data.get('verification_code', '') 282 | if not (code != '' and code == verification_code): 283 | raise forms.ValidationError('验证码不正确') 284 | return self.cleaned_data 285 | -------------------------------------------------------------------------------- /mysite/user/migrations/0001_initial.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.0.5 on 2018-08-26 02:12 2 | 3 | from django.conf import settings 4 | from django.db import migrations, models 5 | import django.db.models.deletion 6 | 7 | 8 | class Migration(migrations.Migration): 9 | 10 | initial = True 11 | 12 | dependencies = [ 13 | migrations.swappable_dependency(settings.AUTH_USER_MODEL), 14 | ] 15 | 16 | operations = [ 17 | migrations.CreateModel( 18 | name='Profile', 19 | fields=[ 20 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 21 | ('nickname', models.CharField(max_length=20)), 22 | ('user', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)), 23 | ], 24 | ), 25 | ] 26 | -------------------------------------------------------------------------------- /mysite/user/migrations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/able8/Django-Course/8cc6cabe7789117f37e009db842b8f8e41d8e5a5/mysite/user/migrations/__init__.py -------------------------------------------------------------------------------- /mysite/user/models.py: -------------------------------------------------------------------------------- 1 | from django.db import models 2 | from django.contrib.auth.models import User 3 | 4 | 5 | class Profile(models.Model): 6 | #一对一关系,一个用户一个资料 7 | user = models.OneToOneField(User, on_delete=models.CASCADE) 8 | nickname = models.CharField(max_length=20, verbose_name='昵称') 9 | 10 | def __str__(self): 11 | return '' % (self.nickname, self.user.username) 12 | 13 | 14 | # 使用类方法的动态绑定,User类绑定获取昵称的方法 15 | def get_nickname(self): 16 | if Profile.objects.filter(user=self).exists(): 17 | profile = Profile.objects.get(user=self) 18 | return profile.nickname 19 | else: 20 | return '' 21 | 22 | def get_nickname_or_username(self): 23 | if Profile.objects.filter(user=self).exists(): 24 | profile = Profile.objects.get(user=self) 25 | return profile.nickname 26 | else: 27 | return self.username 28 | 29 | def has_nickname(self): 30 | return Profile.objects.filter(user=self).exists() 31 | 32 | User.get_nickname = get_nickname 33 | User.has_nickname = has_nickname 34 | User.get_nickname_or_username = get_nickname_or_username -------------------------------------------------------------------------------- /mysite/user/templates/user/bind_email.html: -------------------------------------------------------------------------------- 1 | {% extends "form.html" %} 2 | 3 | {% block other_buttons %} 4 | 5 | {% endblock other_buttons %} 6 | 7 | {% block script_extends %} 8 | 49 | {% endblock script_extends %} -------------------------------------------------------------------------------- /mysite/user/templates/user/forgot_password.html: -------------------------------------------------------------------------------- 1 | {% extends "form.html" %} 2 | 3 | {% block other_buttons %} 4 | 5 | {% endblock other_buttons %} 6 | 7 | {% block script_extends %} 8 | 49 | {% endblock script_extends %} -------------------------------------------------------------------------------- /mysite/user/templates/user/login.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | {% load staticfiles %} 3 | 4 | {% block nav_home_active %}active{% endblock %} 5 | 6 | {% block title %} 7 | 我的网站|登录 8 | {% endblock title %} 9 | {% block header_extends %} 10 | 11 | 12 | {% endblock header_extends %} 13 | 14 | {% block content %} 15 |
16 |
17 |
18 | 19 |
20 |
21 |

登录

22 |
23 |
24 |
25 | {% csrf_token %} 26 | {% for field in login_form %} 27 | 28 | {{ field }} 29 |

30 | {{ field.errors.as_text }} 31 |

32 | {% endfor %} 33 | {{ login_form.non_field_errors }} 34 |
35 | 忘记密码? 36 | 37 |
38 |
39 |
40 | 41 |
42 |
43 |
44 | {% endblock content %} -------------------------------------------------------------------------------- /mysite/user/templates/user/register.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | {% load staticfiles %} 3 | 4 | {% block nav_home_active %}active{% endblock %} 5 | 6 | {% block title %} 7 | 我的网站|注册 8 | {% endblock title %} 9 | {% block header_extends %} 10 | 11 | 12 | {% endblock header_extends %} 13 | 14 | {% block content %} 15 |
16 |
17 |
18 | 19 | {% if not user.is_authenticated %} 20 | 21 |
22 |
23 |

注册

24 |
25 |
26 |
27 | {% csrf_token %} 28 | {% comment %} {{ login_form }} 定制显示 {% endcomment %} 29 | {% for field in reg_form %} 30 | 31 | {{ field }} 32 |

33 | {{ field.errors.as_text }} 34 |

35 | {% endfor %} 36 | {{ reg_form.non_field_errors }} 37 |
38 | 39 | 40 |
41 |
42 |
43 | 44 | {% else %} 45 | 已登录,跳转到首页.... 46 | {% endif %} 49 |
50 |
51 |
52 | {% endblock content %} 53 | 54 | {% block script_extends %} 55 | 96 | {% endblock script_extends %} -------------------------------------------------------------------------------- /mysite/user/templates/user/user_info.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | {% load staticfiles %} 3 | 4 | {% block nav_home_active %}active{% endblock %} 5 | {% block title %} 我的网站 | 个人资料 {% endblock title %} 6 | {% block header_extends %} 7 | 8 | 9 | {% endblock header_extends %} 10 | 11 | l{% block content %} 12 |
13 |
14 |
15 | {% if user.is_authenticated %} 16 |

{{ user.username }}

17 |
    18 |
  • 昵称: {{ user.get_nickname }} 修改昵称
  • 19 |
  • 邮箱: 20 | {% if user.email %} {{ user.email }} 21 | {% else %} 未绑定 绑定邮箱 22 | {% endif %}
  • 23 |
  • 上次登录的时间: {{ user.last_login|date:"Y-m-d H:i:s" }}
  • 24 |
  • 修改密码
  • 25 |
26 | {% else %} 27 | 未登录,跳转到首页.... 28 | 31 | {% endif %} 32 |
33 |
34 |
35 | {% endblock content %} -------------------------------------------------------------------------------- /mysite/user/urls.py: -------------------------------------------------------------------------------- 1 | from django.urls import path 2 | from . import views 3 | 4 | urlpatterns = [ 5 | path('login/', views.login, name='login'), 6 | path('login_for_modal/', views.login_for_modal, name='login_for_modal'), 7 | path('register/', views.register, name='register'), 8 | path('logout/', views.logout, name='logout'), 9 | path('user_info/', views.user_info, name='user_info'), 10 | path('change_nickname/', views.change_nickname, name='change_nickname'), 11 | path('bind_email/', views.bind_email, name='bind_email'), 12 | path('send_verification_code/', views.send_verification_code, name='send_verification_code'), 13 | path('change_password/', views.change_password, name='change_password'), 14 | path('forgot_password/', views.forgot_password, name='forgot_password'), 15 | ] -------------------------------------------------------------------------------- /mysite/user/views.py: -------------------------------------------------------------------------------- 1 | import string 2 | import random 3 | import time 4 | from django.shortcuts import render, redirect 5 | from django.contrib import auth 6 | from django.contrib.auth.models import User 7 | from django.http import JsonResponse 8 | from django.core.mail import send_mail 9 | from django.urls import reverse 10 | from django.conf import settings 11 | from .forms import LoginForm, RegForm, ChangeNicknameForm, BindEmailForm, ChangePasswordForm, ForgotPasswordForm 12 | from .models import Profile 13 | 14 | 15 | def login(request): 16 | if request.method == 'POST': 17 | login_form = LoginForm(request.POST) 18 | if login_form.is_valid(): 19 | user = login_form.cleaned_data['user'] 20 | auth.login(request, user) 21 | return redirect(request.GET.get('from', reverse('home'))) 22 | else: 23 | # get 加载页面 24 | login_form = LoginForm() # 实例化表单 25 | 26 | context = {} 27 | context['login_form'] = login_form 28 | return render(request, 'user/login.html', context) 29 | 30 | 31 | def login_for_modal(request): 32 | login_form = LoginForm(request.POST) 33 | data = {} 34 | if login_form.is_valid(): 35 | user = login_form.cleaned_data['user'] 36 | auth.login(request, user) 37 | data['status'] = 'SUCCESS' 38 | else: 39 | data['status'] = 'ERROR' 40 | return JsonResponse(data) 41 | 42 | 43 | def register(request): 44 | if request.method == 'POST': 45 | reg_form = RegForm(request.POST, request=request) 46 | if reg_form.is_valid(): 47 | username = reg_form.cleaned_data['username'] 48 | password = reg_form.cleaned_data['password'] 49 | email = reg_form.cleaned_data['email'] 50 | # 创建用户 51 | user = User.objects.create_user(username, email, password) 52 | user.save() 53 | # 清除session, 不清楚的话就可以一个验证码多次注册了 54 | del request.session['register_code'] 55 | # 或者 56 | ''' 57 | user = User() 58 | user.username = username 59 | user.email = email 60 | user.set_password(password) 61 | user.save() 62 | ''' 63 | # 登录用户 64 | user = auth.authenticate(username=username, password=password) 65 | auth.login(request, user) 66 | # 跳转注册之前的页面 67 | return redirect(request.GET.get('from', reverse('home'))) 68 | else: 69 | reg_form = RegForm() # 实例化表单 70 | 71 | context = {} 72 | context['reg_form'] = reg_form 73 | return render(request, 'user/register.html', context) 74 | 75 | 76 | def logout(request): 77 | auth.logout(request) 78 | return redirect(request.GET.get('from', reverse('home'))) 79 | 80 | 81 | def user_info(request): 82 | context = {} 83 | return render(request, 'user/user_info.html', context) 84 | 85 | 86 | def change_nickname(request): 87 | redirect_to = request.GET.get('from', reverse('home')) 88 | if request.method == 'POST': 89 | form = ChangeNicknameForm(request.POST, user=request.user) 90 | if form.is_valid(): 91 | nickname_new = form.cleaned_data['nickname_new'] 92 | profile, created = Profile.objects.get_or_create(user=request.user) 93 | profile.nickname = nickname_new 94 | profile.save() 95 | return redirect(redirect_to) 96 | else: 97 | form = ChangeNicknameForm() 98 | 99 | context = {} 100 | context['page_title'] = '修改昵称' 101 | context['form_title'] = '修改昵称' 102 | context['submit_text'] = '修改' 103 | context['form'] = form 104 | context['return_back_url'] = redirect_to 105 | return render(request, 'form.html', context) 106 | 107 | 108 | def bind_email(request): 109 | redirect_to = request.GET.get('from', reverse('home')) 110 | if request.method == 'POST': 111 | form = BindEmailForm(request.POST, request=request) 112 | if form.is_valid(): 113 | email = form.cleaned_data['email'] 114 | request.user.email = email 115 | request.user.save() 116 | # 清除session, 不清楚的话就可以一个验证码多次注册了 117 | del request.session['bind_email_code'] 118 | return redirect(redirect_to) 119 | else: 120 | form = BindEmailForm() 121 | 122 | context = {} 123 | context['page_title'] = '绑定邮箱' 124 | context['form_title'] = '绑定邮箱' 125 | context['submit_text'] = '绑定' 126 | context['form'] = form 127 | context['return_back_url'] = redirect_to 128 | return render(request, 'user/bind_email.html', context) 129 | 130 | 131 | def send_verification_code(request): 132 | email = request.GET.get('email', '') 133 | send_for = request.GET.get('send_for', '') 134 | data = {} 135 | 136 | if email != '': 137 | # 生成验证码 138 | code = ''.join(random.sample(string.digits, 6)) 139 | 140 | now = int(time.time()) # 秒数 141 | send_code_time = request.session.get('send_code_time', 0) 142 | if now - send_code_time < 60: 143 | data['status'] = 'ERROR' 144 | else: 145 | # session 存储用户请求信息,默认有效期两周 146 | request.session[send_for] = code 147 | request.session['send_code_time'] = now 148 | # 发送邮箱 149 | send_mail( 150 | '验证您的电子邮件地址', 151 | '验证码: %s' % code, 152 | settings.EMAIL_HOST_USER, 153 | [email], 154 | fail_silently=False, 155 | ) 156 | data['status'] = 'SUCCESS' 157 | else: 158 | data['status'] = 'ERROR' 159 | return JsonResponse(data) 160 | 161 | 162 | def change_password(request): 163 | redirect_to = reverse('login') 164 | if request.method == 'POST': 165 | form = ChangePasswordForm(request.POST, user=request.user) 166 | if form.is_valid(): 167 | user = request.user 168 | new_password = form.cleaned_data['new_password'] 169 | user.set_password(new_password) 170 | user.save() 171 | auth.logout(request) 172 | return redirect(redirect_to) 173 | else: 174 | form = ChangePasswordForm() 175 | 176 | context = {} 177 | context['page_title'] = '修改密码' 178 | context['form_title'] = '修改密码' 179 | context['submit_text'] = '修改密码' 180 | context['form'] = form 181 | context['return_back_url'] = redirect_to 182 | return render(request, 'form.html', context) 183 | 184 | 185 | def forgot_password(request): 186 | redirect_to = reverse('login') 187 | if request.method == 'POST': 188 | form = ForgotPasswordForm(request.POST, user=request.user) 189 | if form.is_valid(): 190 | email = form.cleaned_data['email'] 191 | new_password = form.cleaned_data['new_password'] 192 | user = User.objects.get(email=email) 193 | user.set_password(new_password) 194 | user.save() 195 | # 清除session 196 | del request.session['forgot_password_code'] 197 | return redirect(redirect_to) 198 | else: 199 | form = ForgotPasswordForm() 200 | 201 | context = {} 202 | context['page_title'] = '重置密码' 203 | context['form_title'] = '重置密码' 204 | context['submit_text'] = '重置' 205 | context['form'] = form 206 | context['return_back_url'] = redirect_to 207 | return render(request, 'user/forgot_password.html', context) --------------------------------------------------------------------------------