Compare commits
16 Commits
| Author | SHA1 | Date |
|---|---|---|
|
|
551fc28714 | 2 months ago |
|
|
ea6f3560fa | 2 months ago |
|
|
14c74f54a3 | 3 months ago |
|
|
45bd443da9 | 3 months ago |
|
|
54067a27dc | 3 months ago |
|
|
cec97bef1b | 3 months ago |
|
|
431d981352 | 3 months ago |
|
|
67acf6b1ca | 4 months ago |
|
|
43ed867e7e | 4 months ago |
|
|
ef3241479a | 4 months ago |
|
|
8292b609ca | 4 months ago |
|
|
2393aa6b14 | 4 months ago |
|
|
734e1c29d3 | 4 months ago |
|
|
04501d47dd | 4 months ago |
|
|
b25e073a21 | 5 months ago |
|
|
c13443ffeb | 5 months ago |
|
Before Width: | Height: | Size: 18 KiB After Width: | Height: | Size: 18 KiB |
|
Before Width: | Height: | Size: 129 KiB After Width: | Height: | Size: 129 KiB |
|
Before Width: | Height: | Size: 24 KiB After Width: | Height: | Size: 24 KiB |
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -0,0 +1,10 @@
|
|||||||
|
[run]
|
||||||
|
source = .
|
||||||
|
include = *.py
|
||||||
|
omit =
|
||||||
|
*migrations*
|
||||||
|
*tests*
|
||||||
|
*.html
|
||||||
|
*whoosh_cn_backend*
|
||||||
|
*settings.py*
|
||||||
|
*venv*
|
||||||
@ -0,0 +1,11 @@
|
|||||||
|
bin/data/
|
||||||
|
# virtualenv
|
||||||
|
venv/
|
||||||
|
collectedstatic/
|
||||||
|
djangoblog/whoosh_index/
|
||||||
|
uploads/
|
||||||
|
settings_production.py
|
||||||
|
*.md
|
||||||
|
docs/
|
||||||
|
logs/
|
||||||
|
static/
|
||||||
@ -0,0 +1,6 @@
|
|||||||
|
blog/static/* linguist-vendored
|
||||||
|
*.js linguist-vendored
|
||||||
|
*.css linguist-vendored
|
||||||
|
* text=auto
|
||||||
|
*.sh text eol=lf
|
||||||
|
*.conf text eol=lf
|
||||||
@ -0,0 +1,80 @@
|
|||||||
|
# Byte-compiled / optimized / DLL files
|
||||||
|
__pycache__/
|
||||||
|
*.py[cod]
|
||||||
|
*$py.class
|
||||||
|
|
||||||
|
# C extensions
|
||||||
|
*.so
|
||||||
|
|
||||||
|
# Distribution / packaging
|
||||||
|
.Python
|
||||||
|
env/
|
||||||
|
build/
|
||||||
|
develop-eggs/
|
||||||
|
dist/
|
||||||
|
downloads/
|
||||||
|
eggs/
|
||||||
|
.eggs/
|
||||||
|
lib/
|
||||||
|
lib64/
|
||||||
|
parts/
|
||||||
|
sdist/
|
||||||
|
var/
|
||||||
|
*.egg-info/
|
||||||
|
.installed.cfg
|
||||||
|
*.egg
|
||||||
|
|
||||||
|
# PyInstaller
|
||||||
|
# Usually these files are written by a python script from a template
|
||||||
|
# before PyInstaller builds the exe, so as to inject date/other infos into it.
|
||||||
|
*.manifest
|
||||||
|
*.spec
|
||||||
|
|
||||||
|
# Installer logs
|
||||||
|
pip-log.txt
|
||||||
|
pip-delete-this-directory.txt
|
||||||
|
|
||||||
|
# Unit test / coverage reports
|
||||||
|
htmlcov/
|
||||||
|
.tox/
|
||||||
|
.coverage
|
||||||
|
.coverage.*
|
||||||
|
.cache
|
||||||
|
nosetests.xml
|
||||||
|
coverage.xml
|
||||||
|
*,cover
|
||||||
|
|
||||||
|
# Translations
|
||||||
|
*.pot
|
||||||
|
|
||||||
|
# Django stuff:
|
||||||
|
*.log
|
||||||
|
logs/
|
||||||
|
|
||||||
|
# Sphinx documentation
|
||||||
|
docs/_build/
|
||||||
|
|
||||||
|
# PyBuilder
|
||||||
|
target/
|
||||||
|
|
||||||
|
|
||||||
|
# PyCharm
|
||||||
|
# http://www.jetbrains.com/pycharm/webhelp/project.html
|
||||||
|
.idea
|
||||||
|
.iml
|
||||||
|
static/
|
||||||
|
# virtualenv
|
||||||
|
venv/
|
||||||
|
|
||||||
|
collectedstatic/
|
||||||
|
djangoblog/whoosh_index/
|
||||||
|
google93fd32dbd906620a.html
|
||||||
|
baidu_verify_FlHL7cUyC9.html
|
||||||
|
BingSiteAuth.xml
|
||||||
|
cb9339dbe2ff86a5aa169d28dba5f615.txt
|
||||||
|
werobot_session.*
|
||||||
|
django.jpg
|
||||||
|
uploads/
|
||||||
|
settings_production.py
|
||||||
|
werobot_session.db
|
||||||
|
bin/datas/
|
||||||
@ -0,0 +1,50 @@
|
|||||||
|
from django.contrib.auth.models import AbstractUser
|
||||||
|
from django.db import models
|
||||||
|
from django.urls import reverse
|
||||||
|
from django.utils.timezone import now
|
||||||
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
from djangoblog.utils import get_current_site
|
||||||
|
|
||||||
|
|
||||||
|
# Create your models here.
|
||||||
|
|
||||||
|
class BlogUser(AbstractUser):
|
||||||
|
nickname = models.CharField(_('nick name'), max_length=100, blank=True)
|
||||||
|
creation_time = models.DateTimeField(_('creation time'), default=now)
|
||||||
|
last_modify_time = models.DateTimeField(_('last modify time'), default=now)
|
||||||
|
source = models.CharField(_('create source'), max_length=100, blank=True)
|
||||||
|
|
||||||
|
def get_absolute_url(self):
|
||||||
|
return reverse(
|
||||||
|
'blog:author_detail', kwargs={
|
||||||
|
'author_name': self.username})
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return self.email
|
||||||
|
|
||||||
|
def get_full_url(self):
|
||||||
|
site = get_current_site().domain
|
||||||
|
url = "https://{site}{path}".format(site=site,
|
||||||
|
path=self.get_absolute_url())
|
||||||
|
return url
|
||||||
|
|
||||||
|
def get_subscriber_count(self):
|
||||||
|
"""获取订阅该作者的用户数量"""
|
||||||
|
from blog.models import Subscription
|
||||||
|
return Subscription.objects.filter(author=self, subscription_type='author').count()
|
||||||
|
|
||||||
|
def get_subscribed_articles_count(self):
|
||||||
|
"""获取该用户订阅的文章数量"""
|
||||||
|
from blog.models import Subscription
|
||||||
|
return Subscription.objects.filter(user=self, subscription_type='article').count()
|
||||||
|
|
||||||
|
def get_subscribed_authors_count(self):
|
||||||
|
"""获取该用户订阅的作者数量"""
|
||||||
|
from blog.models import Subscription
|
||||||
|
return Subscription.objects.filter(user=self, subscription_type='author').count()
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
ordering = ['-id']
|
||||||
|
verbose_name = _('user')
|
||||||
|
verbose_name_plural = verbose_name
|
||||||
|
get_latest_by = 'id'
|
||||||
@ -0,0 +1,73 @@
|
|||||||
|
# Generated by Django 5.2.4 on 2025-11-22 19:27
|
||||||
|
|
||||||
|
import django.db.models.deletion
|
||||||
|
import django.utils.timezone
|
||||||
|
from django.conf import settings
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
("blog", "0007_article_likes_like"),
|
||||||
|
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.RemoveField(
|
||||||
|
model_name="tag",
|
||||||
|
name="creation_time",
|
||||||
|
),
|
||||||
|
migrations.RemoveField(
|
||||||
|
model_name="tag",
|
||||||
|
name="last_modify_time",
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name="tag",
|
||||||
|
name="id",
|
||||||
|
field=models.BigAutoField(
|
||||||
|
auto_created=True, primary_key=True, serialize=False, verbose_name="ID"
|
||||||
|
),
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name="TagSubscription",
|
||||||
|
fields=[
|
||||||
|
(
|
||||||
|
"id",
|
||||||
|
models.BigAutoField(
|
||||||
|
auto_created=True,
|
||||||
|
primary_key=True,
|
||||||
|
serialize=False,
|
||||||
|
verbose_name="ID",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"created_time",
|
||||||
|
models.DateTimeField(
|
||||||
|
default=django.utils.timezone.now, verbose_name="创建时间"
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"tag",
|
||||||
|
models.ForeignKey(
|
||||||
|
on_delete=django.db.models.deletion.CASCADE,
|
||||||
|
to="blog.tag",
|
||||||
|
verbose_name="标签",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"user",
|
||||||
|
models.ForeignKey(
|
||||||
|
on_delete=django.db.models.deletion.CASCADE,
|
||||||
|
to=settings.AUTH_USER_MODEL,
|
||||||
|
verbose_name="用户",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
"verbose_name": "标签订阅",
|
||||||
|
"verbose_name_plural": "标签订阅",
|
||||||
|
"unique_together": {("user", "tag")},
|
||||||
|
},
|
||||||
|
),
|
||||||
|
]
|
||||||
@ -0,0 +1,58 @@
|
|||||||
|
# Generated by Django 5.2.4 on 2025-01-XX XX:XX
|
||||||
|
|
||||||
|
import django.db.models.deletion
|
||||||
|
import django.utils.timezone
|
||||||
|
from django.conf import settings
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('blog', '0008_remove_tag_creation_time_remove_tag_last_modify_time_and_more'),
|
||||||
|
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='article',
|
||||||
|
name='subscriptions',
|
||||||
|
field=models.PositiveIntegerField(default=0, verbose_name='订阅数'),
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='Subscription',
|
||||||
|
fields=[
|
||||||
|
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('subscription_type', models.CharField(choices=[('article', '文章'), ('author', '作者')], default='article', max_length=10, verbose_name='订阅类型')),
|
||||||
|
('created_time', models.DateTimeField(default=django.utils.timezone.now, verbose_name='创建时间')),
|
||||||
|
('article', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='blog.article', verbose_name='订阅的文章')),
|
||||||
|
('author', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='subscribed_by', to=settings.AUTH_USER_MODEL, verbose_name='订阅的作者')),
|
||||||
|
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL, verbose_name='订阅用户')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'verbose_name': '订阅记录',
|
||||||
|
'verbose_name_plural': '订阅记录',
|
||||||
|
},
|
||||||
|
),
|
||||||
|
migrations.AddIndex(
|
||||||
|
model_name='subscription',
|
||||||
|
index=models.Index(fields=['user', 'subscription_type'], name='blog_subscr_user_id_idx'),
|
||||||
|
),
|
||||||
|
migrations.AddIndex(
|
||||||
|
model_name='subscription',
|
||||||
|
index=models.Index(fields=['article'], name='blog_subscr_article_idx'),
|
||||||
|
),
|
||||||
|
migrations.AddIndex(
|
||||||
|
model_name='subscription',
|
||||||
|
index=models.Index(fields=['author'], name='blog_subscr_author_idx'),
|
||||||
|
),
|
||||||
|
migrations.AddIndex(
|
||||||
|
model_name='subscription',
|
||||||
|
index=models.Index(fields=['user', 'article'], name='blog_subscr_user_article_idx'),
|
||||||
|
),
|
||||||
|
migrations.AddIndex(
|
||||||
|
model_name='subscription',
|
||||||
|
index=models.Index(fields=['user', 'author'], name='blog_subscr_user_author_idx'),
|
||||||
|
),
|
||||||
|
]
|
||||||
|
|
||||||
@ -0,0 +1,38 @@
|
|||||||
|
# Generated by Django 5.2.4 on 2025-11-22 20:06
|
||||||
|
|
||||||
|
from django.db import migrations
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
("blog", "0009_article_subscriptions_subscription"),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.RenameIndex(
|
||||||
|
model_name="subscription",
|
||||||
|
new_name="blog_subscr_user_id_ae3348_idx",
|
||||||
|
old_name="blog_subscr_user_id_idx",
|
||||||
|
),
|
||||||
|
migrations.RenameIndex(
|
||||||
|
model_name="subscription",
|
||||||
|
new_name="blog_subscr_article_dc0890_idx",
|
||||||
|
old_name="blog_subscr_article_idx",
|
||||||
|
),
|
||||||
|
migrations.RenameIndex(
|
||||||
|
model_name="subscription",
|
||||||
|
new_name="blog_subscr_author__279c7a_idx",
|
||||||
|
old_name="blog_subscr_author_idx",
|
||||||
|
),
|
||||||
|
migrations.RenameIndex(
|
||||||
|
model_name="subscription",
|
||||||
|
new_name="blog_subscr_user_id_be80aa_idx",
|
||||||
|
old_name="blog_subscr_user_article_idx",
|
||||||
|
),
|
||||||
|
migrations.RenameIndex(
|
||||||
|
model_name="subscription",
|
||||||
|
new_name="blog_subscr_user_id_a74bfc_idx",
|
||||||
|
old_name="blog_subscr_user_author_idx",
|
||||||
|
),
|
||||||
|
]
|
||||||
@ -0,0 +1,45 @@
|
|||||||
|
# Generated by Django 5.2.4 on 2025-11-22 XX:XX
|
||||||
|
|
||||||
|
import django.db.models.deletion
|
||||||
|
import django.utils.timezone
|
||||||
|
from django.conf import settings
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('blog', '0010_rename_blog_subscr_user_id_idx_blog_subscr_user_id_ae3348_idx_and_more'),
|
||||||
|
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='Notification',
|
||||||
|
fields=[
|
||||||
|
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('notification_type', models.CharField(choices=[('article_published', '文章发布'), ('article_updated', '文章更新'), ('author_new_article', '作者新文章')], default='article_published', max_length=20, verbose_name='通知类型')),
|
||||||
|
('title', models.CharField(max_length=200, verbose_name='通知标题')),
|
||||||
|
('content', models.TextField(blank=True, verbose_name='通知内容')),
|
||||||
|
('is_read', models.BooleanField(default=False, verbose_name='已读')),
|
||||||
|
('created_time', models.DateTimeField(default=django.utils.timezone.now, verbose_name='创建时间')),
|
||||||
|
('article', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='blog.article', verbose_name='相关文章')),
|
||||||
|
('author', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='sent_notifications', to=settings.AUTH_USER_MODEL, verbose_name='相关作者')),
|
||||||
|
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='notifications', to=settings.AUTH_USER_MODEL, verbose_name='接收用户')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'verbose_name': '通知',
|
||||||
|
'verbose_name_plural': '通知',
|
||||||
|
'ordering': ['-created_time'],
|
||||||
|
},
|
||||||
|
),
|
||||||
|
migrations.AddIndex(
|
||||||
|
model_name='notification',
|
||||||
|
index=models.Index(fields=['user', 'is_read'], name='blog_notifi_user_id_idx'),
|
||||||
|
),
|
||||||
|
migrations.AddIndex(
|
||||||
|
model_name='notification',
|
||||||
|
index=models.Index(fields=['created_time'], name='blog_notifi_created_idx'),
|
||||||
|
),
|
||||||
|
]
|
||||||
|
|
||||||
@ -0,0 +1,74 @@
|
|||||||
|
from django.urls import path
|
||||||
|
from . import views
|
||||||
|
|
||||||
|
app_name = 'blog'
|
||||||
|
|
||||||
|
urlpatterns = [
|
||||||
|
path(
|
||||||
|
r'',
|
||||||
|
views.IndexView.as_view(),
|
||||||
|
name='index'),
|
||||||
|
path(
|
||||||
|
r'page/<int:page>/',
|
||||||
|
views.IndexView.as_view(),
|
||||||
|
name='index_page'),
|
||||||
|
path(
|
||||||
|
r'article/<int:year>/<int:month>/<int:day>/<int:article_id>.html',
|
||||||
|
views.ArticleDetailView.as_view(),
|
||||||
|
name='detailbyid'),
|
||||||
|
path(
|
||||||
|
r'category/<slug:category_name>.html',
|
||||||
|
views.CategoryDetailView.as_view(),
|
||||||
|
name='category_detail'),
|
||||||
|
path(
|
||||||
|
r'category/<slug:category_name>/<int:page>.html',
|
||||||
|
views.CategoryDetailView.as_view(),
|
||||||
|
name='category_detail_page'),
|
||||||
|
path(
|
||||||
|
r'author/<author_name>.html',
|
||||||
|
views.AuthorDetailView.as_view(),
|
||||||
|
name='author_detail'),
|
||||||
|
path(
|
||||||
|
r'author/<author_name>/<int:page>.html',
|
||||||
|
views.AuthorDetailView.as_view(),
|
||||||
|
name='author_detail_page'),
|
||||||
|
path(
|
||||||
|
'archives.html',
|
||||||
|
views.ArchivesView.as_view(),
|
||||||
|
name='archives'),
|
||||||
|
path(
|
||||||
|
'links.html',
|
||||||
|
views.LinkListView.as_view(),
|
||||||
|
name='links'),
|
||||||
|
path(
|
||||||
|
r'upload',
|
||||||
|
views.fileupload,
|
||||||
|
name='upload'),
|
||||||
|
path(
|
||||||
|
r'clean',
|
||||||
|
views.clean_cache_view,
|
||||||
|
name='clean'),
|
||||||
|
path('article/<int:article_id>/like/', views.like_article, name='like_article'),
|
||||||
|
path('article/<int:article_id>/like/check/', views.check_like_status, name='check_like_status'),
|
||||||
|
# 订阅相关路由 - 必须在标签详情路由之前,避免路由冲突
|
||||||
|
path('article/<int:article_id>/subscribe/', views.subscribe_article, name='subscribe_article'),
|
||||||
|
path('article/<int:article_id>/subscribe/check/', views.check_subscribe_status, name='check_subscribe_status'),
|
||||||
|
path('author/<int:author_id>/subscribe/', views.subscribe_author, name='subscribe_author'),
|
||||||
|
path('author/<int:author_id>/subscribe/check/', views.check_subscribe_status, name='check_author_subscribe_status'),
|
||||||
|
path('tag/<int:tag_id>/subscribe/', views.subscribe_tag, name='subscribe_tag'),
|
||||||
|
path(
|
||||||
|
r'tag/<slug:tag_name>.html',
|
||||||
|
views.TagDetailView.as_view(),
|
||||||
|
name='tag_detail'),
|
||||||
|
path(
|
||||||
|
r'tag/<slug:tag_name>/<int:page>.html',
|
||||||
|
views.TagDetailView.as_view(),
|
||||||
|
name='tag_detail_page'),
|
||||||
|
path('subscription/history/', views.subscription_history, name='subscription_history'),
|
||||||
|
# 通知相关路由
|
||||||
|
path('notifications/', views.notification_list, name='notification_list'),
|
||||||
|
path('notifications/unread/count/', views.get_unread_notification_count, name='unread_notification_count'),
|
||||||
|
path('notifications/recent/', views.get_recent_notifications, name='recent_notifications'),
|
||||||
|
path('notifications/<int:notification_id>/read/', views.mark_notification_read, name='mark_notification_read'),
|
||||||
|
path('notifications/read/all/', views.mark_all_notifications_read, name='mark_all_notifications_read'),
|
||||||
|
]
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in new issue