Compare commits
No commits in common. 'main' and 'main' have entirely different histories.
@ -1,8 +1 @@
|
||||
/.idea/
|
||||
|
||||
# DjangoBlog
|
||||
.env
|
||||
/logs/
|
||||
/collectedstatic/
|
||||
/djangoblog/
|
||||
/static/
|
||||
.idea
|
||||
|
||||
@ -1,21 +0,0 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2025
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
@ -1,53 +0,0 @@
|
||||
digraph "djangoblog" {
|
||||
|
||||
splines = ortho;
|
||||
fontname = "Inconsolata";
|
||||
|
||||
node [colorscheme = ylgnbu4];
|
||||
edge [colorscheme = dark28, dir = both];
|
||||
|
||||
accounts_bloguser [shape = record, pos = "6.458,23.583!" , label = "{ accounts_bloguser | password : varchar(128)\l last_login : datetime(6)\l is_superuser : tinyint(1)\l username : varchar(150)\l first_name : varchar(150)\l last_name : varchar(150)\l email : varchar(254)\l is_staff : tinyint(1)\l is_active : tinyint(1)\l date_joined : datetime(6)\l nickname : varchar(100)\l source : varchar(100)\l creation_time : datetime(6)\l last_modify_time : datetime(6)\l| id : bigint(20)\l }"];
|
||||
accounts_bloguser_groups [shape = record, pos = "2.597,19.375!" , label = "{ accounts_bloguser_groups | bloguser_id : bigint(20)\l group_id : int(11)\l| id : bigint(20)\l }"];
|
||||
accounts_bloguser_user_permissions [shape = record, pos = "1.444,22.750!" , label = "{ accounts_bloguser_user_permissions | bloguser_id : bigint(20)\l permission_id : int(11)\l| id : bigint(20)\l }"];
|
||||
auth_group [shape = record, pos = "0.069,19.778!" , label = "{ auth_group | name : varchar(150)\l| id : int(11)\l }"];
|
||||
auth_group_permissions [shape = record, pos = "-2.167,22.111!" , label = "{ auth_group_permissions | group_id : int(11)\l permission_id : int(11)\l| id : bigint(20)\l }"];
|
||||
auth_permission [shape = record, pos = "-0.556,25.153!" , label = "{ auth_permission | name : varchar(255)\l content_type_id : int(11)\l codename : varchar(100)\l| id : int(11)\l }"];
|
||||
blog_article [shape = record, pos = "10.875,25.528!" , label = "{ blog_article | title : varchar(200)\l body : longtext\l pub_time : datetime(6)\l status : varchar(1)\l comment_status : varchar(1)\l type : varchar(1)\l views : int(10) unsigned\l article_order : int(11)\l show_toc : tinyint(1)\l author_id : bigint(20)\l category_id : int(11)\l creation_time : datetime(6)\l last_modify_time : datetime(6)\l| id : int(11)\l }"];
|
||||
blog_article_tags [shape = record, pos = "14.750,22.736!" , label = "{ blog_article_tags | article_id : int(11)\l tag_id : int(11)\l| id : bigint(20)\l }"];
|
||||
blog_blogsettings [shape = record, pos = "-2.167,12.972!" , label = "{ blog_blogsettings | site_name : varchar(200)\l site_description : longtext\l site_seo_description : longtext\l site_keywords : longtext\l article_sub_length : int(11)\l sidebar_article_count : int(11)\l sidebar_comment_count : int(11)\l article_comment_count : int(11)\l show_google_adsense : tinyint(1)\l google_adsense_codes : longtext\l open_site_comment : tinyint(1)\l beian_code : varchar(2000)\l analytics_code : longtext\l show_gongan_code : tinyint(1)\l gongan_beiancode : longtext\l global_footer : longtext\l global_header : longtext\l comment_need_review : tinyint(1)\l| id : bigint(20)\l }"];
|
||||
blog_category [shape = record, pos = "12.556,28.889!" , label = "{ blog_category | name : varchar(30)\l slug : varchar(60)\l index : int(11)\l parent_category_id : int(11)\l creation_time : datetime(6)\l last_modify_time : datetime(6)\l| id : int(11)\l }"];
|
||||
blog_links [shape = record, pos = "5.097,12.972!" , label = "{ blog_links | name : varchar(30)\l link : varchar(200)\l sequence : int(11)\l is_enable : tinyint(1)\l show_type : varchar(1)\l last_mod_time : datetime(6)\l creation_time : datetime(6)\l| id : bigint(20)\l }"];
|
||||
blog_sidebar [shape = record, pos = "8.361,12.972!" , label = "{ blog_sidebar | name : varchar(100)\l content : longtext\l sequence : int(11)\l is_enable : tinyint(1)\l last_mod_time : datetime(6)\l creation_time : datetime(6)\l| id : bigint(20)\l }"];
|
||||
blog_tag [shape = record, pos = "17.569,22.014!" , label = "{ blog_tag | name : varchar(30)\l slug : varchar(60)\l creation_time : datetime(6)\l last_modify_time : datetime(6)\l| id : int(11)\l }"];
|
||||
comments_comment [shape = record, pos = "10.292,19.931!" , label = "{ comments_comment | body : longtext\l is_enable : tinyint(1)\l article_id : int(11)\l author_id : bigint(20)\l parent_comment_id : bigint(20)\l creation_time : datetime(6)\l last_modify_time : datetime(6)\l| id : bigint(20)\l }"];
|
||||
django_admin_log [shape = record, pos = "4.917,27.264!" , label = "{ django_admin_log | action_time : datetime(6)\l object_id : longtext\l object_repr : varchar(200)\l action_flag : smallint(5) unsigned\l change_message : longtext\l content_type_id : int(11)\l user_id : bigint(20)\l| id : int(11)\l }"];
|
||||
django_content_type [shape = record, pos = "1.625,27.236!" , label = "{ django_content_type | app_label : varchar(100)\l model : varchar(100)\l| id : int(11)\l }"];
|
||||
django_migrations [shape = record, pos = "-2.167,5.722!" , label = "{ django_migrations | app : varchar(255)\l name : varchar(255)\l applied : datetime(6)\l| id : bigint(20)\l }"];
|
||||
django_session [shape = record, pos = "0.917,5.722!" , label = "{ django_session | session_data : longtext\l expire_date : datetime(6)\l| session_key : varchar(40)\l }"];
|
||||
django_site [shape = record, pos = "3.931,5.722!" , label = "{ django_site | domain : varchar(100)\l name : varchar(50)\l| id : int(11)\l }"];
|
||||
oauth_oauthconfig [shape = record, pos = "1.611,12.972!" , label = "{ oauth_oauthconfig | type : varchar(10)\l appkey : varchar(200)\l appsecret : varchar(200)\l callback_url : varchar(200)\l is_enable : tinyint(1)\l creation_time : datetime(6)\l last_modify_time : datetime(6)\l| id : bigint(20)\l }"];
|
||||
oauth_oauthuser [shape = record, pos = "6.472,17.667!" , label = "{ oauth_oauthuser | openid : varchar(50)\l nickname : varchar(50)\l token : varchar(150)\l picture : varchar(350)\l type : varchar(50)\l email : varchar(50)\l metadata : longtext\l author_id : bigint(20)\l creation_time : datetime(6)\l last_modify_time : datetime(6)\l| id : bigint(20)\l }"];
|
||||
owntracks_owntracklog [shape = record, pos = "19.708,12.972!" , label = "{ owntracks_owntracklog | tid : varchar(100)\l lat : double\l lon : double\l creation_time : datetime(6)\l| id : bigint(20)\l }"];
|
||||
servermanager_commands [shape = record, pos = "15.792,12.972!" , label = "{ servermanager_commands | title : varchar(300)\l command : varchar(2000)\l describe : varchar(300)\l creation_time : datetime(6)\l last_modify_time : datetime(6)\l| id : bigint(20)\l }"];
|
||||
servermanager_emailsendlog [shape = record, pos = "11.625,12.972!" , label = "{ servermanager_emailsendlog | emailto : varchar(300)\l title : varchar(2000)\l content : longtext\l send_result : tinyint(1)\l creation_time : datetime(6)\l| id : bigint(20)\l }"];
|
||||
|
||||
accounts_bloguser_groups -> accounts_bloguser [color = "#595959", style = solid , arrowtail = none , arrowhead = normal , taillabel = "", label = "bloguser_id:id", headlabel = ""];
|
||||
accounts_bloguser_groups -> auth_group [color = "#595959", style = solid , arrowtail = none , arrowhead = normal , taillabel = "", label = "group_id:id", headlabel = ""];
|
||||
accounts_bloguser_user_permissions -> accounts_bloguser [color = "#595959", style = solid , arrowtail = none , arrowhead = normal , taillabel = "", label = "bloguser_id:id", headlabel = ""];
|
||||
accounts_bloguser_user_permissions -> auth_permission [color = "#595959", style = solid , arrowtail = none , arrowhead = normal , taillabel = "", label = "permission_id:id", headlabel = ""];
|
||||
auth_group_permissions -> auth_group [color = "#595959", style = solid , arrowtail = none , arrowhead = normal , taillabel = "", label = "group_id:id", headlabel = ""];
|
||||
auth_group_permissions -> auth_permission [color = "#595959", style = solid , arrowtail = none , arrowhead = normal , taillabel = "", label = "permission_id:id", headlabel = ""];
|
||||
auth_permission -> django_content_type [color = "#595959", style = solid , arrowtail = none , arrowhead = normal , taillabel = "", label = "content_type_id:id", headlabel = ""];
|
||||
blog_article -> accounts_bloguser [color = "#595959", style = solid , arrowtail = none , arrowhead = normal , taillabel = "", label = "author_id:id", headlabel = ""];
|
||||
blog_article -> blog_category [color = "#595959", style = solid , arrowtail = none , arrowhead = normal , taillabel = "", label = "category_id:id", headlabel = ""];
|
||||
blog_article_tags -> blog_article [color = "#595959", style = solid , arrowtail = none , arrowhead = normal , taillabel = "", label = "article_id:id", headlabel = ""];
|
||||
blog_article_tags -> blog_tag [color = "#595959", style = solid , arrowtail = none , arrowhead = normal , taillabel = "", label = "tag_id:id", headlabel = ""];
|
||||
blog_category -> blog_category [color = "#595959", style = solid , arrowtail = none , arrowhead = normal , taillabel = "", label = "parent_category_id:id", headlabel = ""];
|
||||
comments_comment -> accounts_bloguser [color = "#595959", style = solid , arrowtail = none , arrowhead = normal , taillabel = "", label = "author_id:id", headlabel = ""];
|
||||
comments_comment -> blog_article [color = "#595959", style = solid , arrowtail = none , arrowhead = normal , taillabel = "", label = "article_id:id", headlabel = ""];
|
||||
comments_comment -> comments_comment [color = "#595959", style = solid , arrowtail = none , arrowhead = normal , taillabel = "", label = "parent_comment_id:id", headlabel = ""];
|
||||
django_admin_log -> accounts_bloguser [color = "#595959", style = solid , arrowtail = none , arrowhead = normal , taillabel = "", label = "user_id:id", headlabel = ""];
|
||||
django_admin_log -> django_content_type [color = "#595959", style = solid , arrowtail = none , arrowhead = normal , taillabel = "", label = "content_type_id:id", headlabel = ""];
|
||||
oauth_oauthuser -> accounts_bloguser [color = "#595959", style = solid , arrowtail = none , arrowhead = normal , taillabel = "", label = "author_id:id", headlabel = ""];
|
||||
|
||||
}
|
||||
|
Before Width: | Height: | Size: 172 KiB |
|
Before Width: | Height: | Size: 1.1 MiB |
|
Before Width: | Height: | Size: 33 KiB |
|
Before Width: | Height: | Size: 86 KiB |
|
Before Width: | Height: | Size: 668 KiB |
|
Before Width: | Height: | Size: 1.9 MiB |
|
Before Width: | Height: | Size: 400 KiB |
|
Before Width: | Height: | Size: 2.2 MiB |
|
Before Width: | Height: | Size: 966 KiB |
|
Before Width: | Height: | Size: 260 KiB |
|
Before Width: | Height: | Size: 728 KiB |
|
Before Width: | Height: | Size: 536 KiB |
@ -1,22 +0,0 @@
|
||||
graph TD
|
||||
subgraph DjangoBlog Project
|
||||
A[accounts] -->|用户体系| B[blog]
|
||||
A -->|用户登录| C[comments]
|
||||
A -->|用户绑定| D[oauth]
|
||||
A -->|用户轨迹| E[owntracks]
|
||||
|
||||
B -->|文章内容| C
|
||||
B -->|搜索功能| F[djangoblog]
|
||||
B -->|插件扩展| G[plugins]
|
||||
B -->|API接口| H[servermanager]
|
||||
|
||||
F -->|搜索引擎| B
|
||||
F -->|站点地图| B
|
||||
|
||||
G -->|插件功能| B
|
||||
|
||||
H -->|邮件/命令| B
|
||||
H -->|微信处理| B
|
||||
|
||||
D -->|第三方登录| A
|
||||
end
|
||||
@ -1,162 +0,0 @@
|
||||
---
|
||||
config:
|
||||
layout: elk
|
||||
---
|
||||
erDiagram
|
||||
BlogUser {
|
||||
int id PK
|
||||
string username
|
||||
string email
|
||||
string nickname
|
||||
datetime creation_time
|
||||
datetime last_modify_time
|
||||
string source
|
||||
}
|
||||
|
||||
Article {
|
||||
int id PK
|
||||
string title
|
||||
text body
|
||||
datetime pub_time
|
||||
char status
|
||||
char comment_status
|
||||
char type
|
||||
int views
|
||||
int author_id FK
|
||||
int article_order
|
||||
boolean show_toc
|
||||
int category_id FK
|
||||
}
|
||||
|
||||
Category {
|
||||
int id PK
|
||||
string name
|
||||
int parent_category_id FK
|
||||
string slug
|
||||
int index
|
||||
}
|
||||
|
||||
Tag {
|
||||
int id PK
|
||||
string name
|
||||
string slug
|
||||
}
|
||||
|
||||
Comment {
|
||||
int id PK
|
||||
text body
|
||||
datetime creation_time
|
||||
datetime last_modify_time
|
||||
int author_id FK
|
||||
int article_id FK
|
||||
int parent_comment_id FK
|
||||
boolean is_enable
|
||||
}
|
||||
|
||||
OAuthUser {
|
||||
int id PK
|
||||
int author_id FK
|
||||
string openid
|
||||
string nickname
|
||||
string token
|
||||
string picture
|
||||
string type
|
||||
string email
|
||||
text metadata
|
||||
datetime creation_time
|
||||
datetime last_modify_time
|
||||
}
|
||||
|
||||
OAuthConfig {
|
||||
int id PK
|
||||
string type
|
||||
string appkey
|
||||
string appsecret
|
||||
string callback_url
|
||||
boolean is_enable
|
||||
datetime creation_time
|
||||
datetime last_modify_time
|
||||
}
|
||||
|
||||
BlogSettings {
|
||||
int id PK
|
||||
string site_name
|
||||
text site_description
|
||||
text site_seo_description
|
||||
text site_keywords
|
||||
int article_sub_length
|
||||
int sidebar_article_count
|
||||
int sidebar_comment_count
|
||||
int article_comment_count
|
||||
boolean show_google_adsense
|
||||
text google_adsense_codes
|
||||
boolean open_site_comment
|
||||
text global_header
|
||||
text global_footer
|
||||
string beian_code
|
||||
text analytics_code
|
||||
boolean show_gongan_code
|
||||
text gongan_beiancode
|
||||
boolean comment_need_review
|
||||
}
|
||||
|
||||
SideBar {
|
||||
int id PK
|
||||
string name
|
||||
text content
|
||||
int sequence
|
||||
boolean is_enable
|
||||
datetime creation_time
|
||||
datetime last_mod_time
|
||||
}
|
||||
|
||||
Links {
|
||||
int id PK
|
||||
string name
|
||||
string link
|
||||
int sequence
|
||||
boolean is_enable
|
||||
char show_type
|
||||
datetime creation_time
|
||||
datetime last_mod_time
|
||||
}
|
||||
|
||||
OwnTrackLog {
|
||||
int id PK
|
||||
string tid
|
||||
float lat
|
||||
float lon
|
||||
datetime creation_time
|
||||
}
|
||||
|
||||
commands {
|
||||
int id PK
|
||||
string title
|
||||
string command
|
||||
string describe
|
||||
datetime creation_time
|
||||
datetime last_modify_time
|
||||
}
|
||||
|
||||
EmailSendLog {
|
||||
int id PK
|
||||
string emailto
|
||||
string title
|
||||
text content
|
||||
boolean send_result
|
||||
datetime creation_time
|
||||
}
|
||||
|
||||
BlogUser ||--o{ Article : "作者发表"
|
||||
BlogUser ||--o{ Comment : "用户评论"
|
||||
BlogUser ||--o{ OAuthUser : "关联第三方账号"
|
||||
|
||||
Article ||--o{ Comment : "文章拥有评论"
|
||||
Article }o--|| Category : "属于分类"
|
||||
Article }o--o{ Tag : "包含标签"
|
||||
|
||||
Category ||--o{ Category : "父分类"
|
||||
|
||||
Comment ||--o{ Comment : "父评论"
|
||||
|
||||
OAuthUser }o--|| OAuthConfig : "使用配置"
|
||||
@ -1,133 +0,0 @@
|
||||
---
|
||||
config:
|
||||
layout: elk
|
||||
---
|
||||
|
||||
classDiagram
|
||||
%% 模型类
|
||||
class BlogUser {
|
||||
+String nickname
|
||||
+DateTime creation_time
|
||||
+DateTime last_modify_time
|
||||
+String source
|
||||
+get_absolute_url()
|
||||
+get_full_url()
|
||||
}
|
||||
|
||||
%% 表单类
|
||||
class BlogUserCreationForm {
|
||||
+CharField password1
|
||||
+CharField password2
|
||||
+clean_password2()
|
||||
+save()
|
||||
}
|
||||
|
||||
class BlogUserChangeForm {
|
||||
+__init__()
|
||||
}
|
||||
|
||||
class LoginForm {
|
||||
+__init__()
|
||||
}
|
||||
|
||||
class RegisterForm {
|
||||
+__init__()
|
||||
+clean_email()
|
||||
}
|
||||
|
||||
class ForgetPasswordForm {
|
||||
+CharField new_password1
|
||||
+CharField new_password2
|
||||
+EmailField email
|
||||
+CharField code
|
||||
+clean_new_password2()
|
||||
+clean_email()
|
||||
+clean_code()
|
||||
}
|
||||
|
||||
class ForgetPasswordCodeForm {
|
||||
+EmailField email
|
||||
}
|
||||
|
||||
%% 视图类
|
||||
class RegisterView {
|
||||
+form_valid()
|
||||
}
|
||||
|
||||
class LogoutView {
|
||||
+get()
|
||||
}
|
||||
|
||||
class LoginView {
|
||||
+get_context_data()
|
||||
+form_valid()
|
||||
+get_success_url()
|
||||
}
|
||||
|
||||
class ForgetPasswordView {
|
||||
+form_valid()
|
||||
}
|
||||
|
||||
class ForgetPasswordEmailCode {
|
||||
+post()
|
||||
}
|
||||
|
||||
%% 认证后端
|
||||
class EmailOrUsernameModelBackend {
|
||||
+authenticate()
|
||||
+get_user()
|
||||
}
|
||||
|
||||
%% 应用配置
|
||||
class AccountsConfig
|
||||
|
||||
%% 测试类
|
||||
class AccountTest
|
||||
|
||||
%% 工具函数
|
||||
class utils {
|
||||
+send_verify_email()
|
||||
+verify()
|
||||
+set_code()
|
||||
+get_code()
|
||||
}
|
||||
|
||||
%% 继承关系
|
||||
BlogUser --|> AbstractUser
|
||||
BlogUserCreationForm --|> ModelForm
|
||||
BlogUserChangeForm --|> UserChangeForm
|
||||
LoginForm --|> AuthenticationForm
|
||||
RegisterForm --|> UserCreationForm
|
||||
ForgetPasswordForm --|> Form
|
||||
ForgetPasswordCodeForm --|> Form
|
||||
RegisterView --|> FormView
|
||||
LogoutView --|> RedirectView
|
||||
LoginView --|> FormView
|
||||
ForgetPasswordView --|> FormView
|
||||
ForgetPasswordEmailCode --|> View
|
||||
EmailOrUsernameModelBackend --|> ModelBackend
|
||||
AccountsConfig --|> AppConfig
|
||||
AccountTest --|> TestCase
|
||||
|
||||
%% 关联关系
|
||||
BlogUserCreationForm --> BlogUser : 创建
|
||||
BlogUserChangeForm --> BlogUser : 修改
|
||||
RegisterForm --> BlogUser : 注册
|
||||
ForgetPasswordForm --> BlogUser : 重置密码
|
||||
RegisterView --> RegisterForm : 使用
|
||||
LoginView --> LoginForm : 使用
|
||||
ForgetPasswordView --> ForgetPasswordForm : 使用
|
||||
ForgetPasswordEmailCode --> ForgetPasswordCodeForm : 使用
|
||||
EmailOrUsernameModelBackend --> BlogUser : 认证
|
||||
AccountTest --> BlogUser : 测试
|
||||
AccountTest --> utils : 测试
|
||||
ForgetPasswordForm --> utils : 验证
|
||||
ForgetPasswordEmailCode --> utils : 发送邮件
|
||||
RegisterView --> utils : 发送邮件
|
||||
|
||||
%% 依赖关系
|
||||
views ..> utils : 导入
|
||||
forms ..> BlogUser : 导入
|
||||
tests ..> BlogUser : 导入
|
||||
tests ..> utils : 导入
|
||||
admin ..> BlogUser : 导入
|
||||
@ -1,266 +0,0 @@
|
||||
---
|
||||
config:
|
||||
layout: elk
|
||||
---
|
||||
|
||||
classDiagram
|
||||
%% 基础模型类
|
||||
class BaseModel {
|
||||
<<abstract>>
|
||||
+id
|
||||
+creation_time
|
||||
+last_modify_time
|
||||
+save()
|
||||
+get_full_url()
|
||||
+get_absolute_url()*
|
||||
}
|
||||
|
||||
%% 核心模型类
|
||||
class Article {
|
||||
+title
|
||||
+body
|
||||
+pub_time
|
||||
+status
|
||||
+comment_status
|
||||
+type
|
||||
+views
|
||||
+article_order
|
||||
+show_toc
|
||||
+body_to_string()
|
||||
+get_category_tree()
|
||||
+viewed()
|
||||
+comment_list()
|
||||
+get_admin_url()
|
||||
+next_article()
|
||||
+prev_article()
|
||||
+get_first_image_url()
|
||||
}
|
||||
|
||||
class Category {
|
||||
+name
|
||||
+parent_category
|
||||
+slug
|
||||
+index
|
||||
+get_category_tree()
|
||||
+get_sub_categorys()
|
||||
}
|
||||
|
||||
class Tag {
|
||||
+name
|
||||
+slug
|
||||
+get_article_count()
|
||||
}
|
||||
|
||||
class Links {
|
||||
+name
|
||||
+link
|
||||
+sequence
|
||||
+is_enable
|
||||
+show_type
|
||||
}
|
||||
|
||||
class SideBar {
|
||||
+name
|
||||
+content
|
||||
+sequence
|
||||
+is_enable
|
||||
}
|
||||
|
||||
class BlogSettings {
|
||||
+site_name
|
||||
+site_description
|
||||
+site_seo_description
|
||||
+site_keywords
|
||||
+article_sub_length
|
||||
+sidebar_article_count
|
||||
+sidebar_comment_count
|
||||
+article_comment_count
|
||||
+show_google_adsense
|
||||
+google_adsense_codes
|
||||
+open_site_comment
|
||||
+global_header
|
||||
+global_footer
|
||||
+beian_code
|
||||
+analytics_code
|
||||
+show_gongan_code
|
||||
+gongan_beiancode
|
||||
+comment_need_review
|
||||
+clean()
|
||||
}
|
||||
|
||||
%% 枚举类型
|
||||
class LinkShowType {
|
||||
<<enumeration>>
|
||||
I, L, P, A, S
|
||||
}
|
||||
|
||||
%% 表单类
|
||||
class BlogSearchForm {
|
||||
+querydata
|
||||
+search()
|
||||
}
|
||||
|
||||
%% 管理类
|
||||
class ArticlelAdmin {
|
||||
+list_display
|
||||
+list_filter
|
||||
+actions
|
||||
+link_to_category()
|
||||
+get_form()
|
||||
+get_view_on_site_url()
|
||||
}
|
||||
|
||||
class TagAdmin
|
||||
class CategoryAdmin
|
||||
class LinksAdmin
|
||||
class SideBarAdmin
|
||||
class BlogSettingsAdmin
|
||||
|
||||
%% 视图类
|
||||
class ArticleListView {
|
||||
<<abstract>>
|
||||
+page_type
|
||||
+link_type
|
||||
+get_queryset_cache_key()*
|
||||
+get_queryset_data()*
|
||||
+get_queryset()
|
||||
+get_context_data()
|
||||
}
|
||||
|
||||
class IndexView
|
||||
class ArticleDetailView {
|
||||
+get_context_data()
|
||||
}
|
||||
class CategoryDetailView
|
||||
class AuthorDetailView
|
||||
class TagDetailView
|
||||
class ArchivesView
|
||||
class LinkListView
|
||||
class EsSearchView
|
||||
|
||||
%% 中间件类
|
||||
class OnlineMiddleware {
|
||||
+__call__()
|
||||
}
|
||||
|
||||
%% 搜索索引类
|
||||
class ArticleIndex {
|
||||
+text
|
||||
+get_model()
|
||||
+index_queryset()
|
||||
}
|
||||
|
||||
%% Elasticsearch 文档类
|
||||
class GeoIp {
|
||||
+continent_name
|
||||
+country_iso_code
|
||||
+country_name
|
||||
+location
|
||||
}
|
||||
|
||||
class UserAgentBrowser
|
||||
class UserAgentOS
|
||||
class UserAgentDevice
|
||||
class UserAgent
|
||||
|
||||
class ElapsedTimeDocument {
|
||||
+url
|
||||
+time_taken
|
||||
+log_datetime
|
||||
+ip
|
||||
+geoip
|
||||
+useragent
|
||||
}
|
||||
|
||||
class ElaspedTimeDocumentManager {
|
||||
+build_index()
|
||||
+delete_index()
|
||||
+create()
|
||||
}
|
||||
|
||||
class ArticleDocument {
|
||||
+body
|
||||
+title
|
||||
+author
|
||||
+category
|
||||
+tags
|
||||
+pub_time
|
||||
+status
|
||||
+comment_status
|
||||
+type
|
||||
+views
|
||||
+article_order
|
||||
}
|
||||
|
||||
class ArticleDocumentManager {
|
||||
+create_index()
|
||||
+delete_index()
|
||||
+convert_to_doc()
|
||||
+rebuild()
|
||||
+update_docs()
|
||||
}
|
||||
|
||||
%% 应用配置类
|
||||
class BlogConfig
|
||||
|
||||
%% 测试类
|
||||
class ArticleTest
|
||||
|
||||
%% 继承关系
|
||||
BaseModel <|-- Article
|
||||
BaseModel <|-- Category
|
||||
BaseModel <|-- Tag
|
||||
ArticleListView <|-- IndexView
|
||||
ArticleListView <|-- CategoryDetailView
|
||||
ArticleListView <|-- AuthorDetailView
|
||||
ArticleListView <|-- TagDetailView
|
||||
ArticleListView <|-- ArchivesView
|
||||
ArticleDetailView --|> DetailView
|
||||
LinkListView --|> ListView
|
||||
EsSearchView --|> SearchView
|
||||
UserAgentOS --|> UserAgentBrowser
|
||||
|
||||
%% 关联关系
|
||||
Article --> Category : Foreign Key
|
||||
Article --> Tag : Many-to-Many
|
||||
Article --> settings.AUTH_USER_MODEL : Foreign Key
|
||||
Category --> Category : Self-reference (Parent Category)
|
||||
Links --> LinkShowType : Uses
|
||||
ArticleDocument --> Article : Mapping
|
||||
ElapsedTimeDocument --> GeoIp : Contains
|
||||
ElapsedTimeDocument --> UserAgent : Contains
|
||||
UserAgent --> UserAgentBrowser : Contains
|
||||
UserAgent --> UserAgentOS : Contains
|
||||
UserAgent --> UserAgentDevice : Contains
|
||||
|
||||
%% 管理关系
|
||||
ArticlelAdmin --> Article : 管理
|
||||
TagAdmin --> Tag : 管理
|
||||
CategoryAdmin --> Category : 管理
|
||||
LinksAdmin --> Links : 管理
|
||||
SideBarAdmin --> SideBar : 管理
|
||||
BlogSettingsAdmin --> BlogSettings : 管理
|
||||
|
||||
%% 视图与模型关系
|
||||
IndexView --> Article : 查询
|
||||
ArticleDetailView --> Article : 详情
|
||||
CategoryDetailView --> Category : 查询
|
||||
AuthorDetailView --> Article : 查询
|
||||
TagDetailView --> Tag : 查询
|
||||
ArchivesView --> Article : 查询
|
||||
LinkListView --> Links : 查询
|
||||
|
||||
%% 搜索关系
|
||||
ArticleIndex --> Article : 索引
|
||||
BlogSearchForm --> SearchForm : 继承
|
||||
|
||||
%% 文档管理关系
|
||||
ArticleDocumentManager --> ArticleDocument : 管理
|
||||
ElaspedTimeDocumentManager --> ElapsedTimeDocument : 管理
|
||||
|
||||
%% 测试关系
|
||||
ArticleTest --> Article : 测试
|
||||
ArticleTest --> Category : 测试
|
||||
ArticleTest --> Tag : 测试
|
||||
ArticleTest --> SideBar : 测试
|
||||
ArticleTest --> Links : 测试
|
||||
@ -1,77 +0,0 @@
|
||||
---
|
||||
config:
|
||||
layout: elk
|
||||
---
|
||||
|
||||
classDiagram
|
||||
%% 模型类
|
||||
class Comment {
|
||||
+body
|
||||
+creation_time
|
||||
+last_modify_time
|
||||
+is_enable
|
||||
+__str__()
|
||||
}
|
||||
|
||||
%% 表单类
|
||||
class CommentForm {
|
||||
+parent_comment_id
|
||||
}
|
||||
|
||||
%% 视图类
|
||||
class CommentPostView {
|
||||
+dispatch()
|
||||
+get()
|
||||
+form_invalid()
|
||||
+form_valid()
|
||||
}
|
||||
|
||||
%% 管理类
|
||||
class CommentAdmin {
|
||||
+list_display
|
||||
+list_filter
|
||||
+actions
|
||||
+link_to_userinfo()
|
||||
+link_to_article()
|
||||
}
|
||||
|
||||
%% 应用配置类
|
||||
class CommentsConfig
|
||||
|
||||
%% 测试类
|
||||
class CommentsTest
|
||||
|
||||
%% 工具函数
|
||||
class utils {
|
||||
+send_comment_email()
|
||||
}
|
||||
|
||||
%% 继承关系
|
||||
CommentForm --|> ModelForm
|
||||
CommentPostView --|> FormView
|
||||
CommentAdmin --|> ModelAdmin
|
||||
CommentsConfig --|> AppConfig
|
||||
CommentsTest --|> TransactionTestCase
|
||||
|
||||
%% 关联关系
|
||||
Comment --> AUTH_USER_MODEL : author
|
||||
Comment --> Article : article
|
||||
Comment --> Comment : parent_comment
|
||||
CommentForm --> Comment : create
|
||||
CommentPostView --> CommentForm : use
|
||||
CommentPostView --> Comment : create
|
||||
CommentAdmin --> Comment : manage
|
||||
CommentsTest --> Comment : test
|
||||
|
||||
%% 依赖关系
|
||||
CommentPostView ..> BlogUser : import
|
||||
CommentPostView ..> Article : import
|
||||
CommentsTest ..> BlogUser : import
|
||||
CommentsTest ..> Article : import
|
||||
CommentsTest ..> Category : import
|
||||
utils ..> Comment : import
|
||||
CommentAdmin ..> Comment : import
|
||||
|
||||
%% 方法调用关系
|
||||
CommentPostView --> utils : possible_call
|
||||
CommentsTest --> utils : test_call
|
||||
@ -1,264 +0,0 @@
|
||||
---
|
||||
config:
|
||||
layout: elk
|
||||
---
|
||||
classDiagram
|
||||
%% 管理站点类
|
||||
class DjangoBlogAdminSite {
|
||||
+site_header
|
||||
+site_title
|
||||
+has_permission()
|
||||
}
|
||||
|
||||
%% 应用配置类
|
||||
class DjangoblogAppConfig {
|
||||
+ready()
|
||||
}
|
||||
|
||||
%% 信号处理器
|
||||
class blog_signals {
|
||||
<<module>>
|
||||
+send_email_signal
|
||||
+oauth_user_login_signal
|
||||
+send_email_signal_handler()
|
||||
+oauth_user_login_signal_handler()
|
||||
+model_post_save_callback()
|
||||
+user_auth_callback()
|
||||
}
|
||||
|
||||
%% 搜索引擎相关类
|
||||
class ElasticSearchBackend {
|
||||
+manager
|
||||
+search()
|
||||
+get_suggestion()
|
||||
+_create()
|
||||
+_delete()
|
||||
+_rebuild()
|
||||
+update()
|
||||
+remove()
|
||||
+clear()
|
||||
}
|
||||
|
||||
class ElasticSearchQuery {
|
||||
+_convert_datetime()
|
||||
+clean()
|
||||
+build_query_fragment()
|
||||
+get_count()
|
||||
+get_spelling_suggestion()
|
||||
}
|
||||
|
||||
class ElasticSearchModelSearchForm {
|
||||
+search()
|
||||
}
|
||||
|
||||
class ElasticSearchEngine
|
||||
|
||||
class WhooshSearchBackend {
|
||||
+RESERVED_WORDS
|
||||
+RESERVED_CHARACTERS
|
||||
+setup()
|
||||
+build_schema()
|
||||
+update()
|
||||
+remove()
|
||||
+clear()
|
||||
+delete_index()
|
||||
+optimize()
|
||||
+search()
|
||||
+more_like_this()
|
||||
+_process_results()
|
||||
+create_spelling_suggestion()
|
||||
}
|
||||
|
||||
class WhooshSearchQuery {
|
||||
+_convert_datetime()
|
||||
+clean()
|
||||
+build_query_fragment()
|
||||
}
|
||||
|
||||
class WhooshEngine
|
||||
|
||||
class WhooshHtmlFormatter {
|
||||
+template
|
||||
}
|
||||
|
||||
%% RSS订阅类
|
||||
class DjangoBlogFeed {
|
||||
+feed_type
|
||||
+description
|
||||
+title
|
||||
+link
|
||||
+author_name()
|
||||
+author_link()
|
||||
+items()
|
||||
+item_title()
|
||||
+item_description()
|
||||
+feed_copyright()
|
||||
+item_link()
|
||||
+item_guid()
|
||||
}
|
||||
|
||||
%% 日志管理类
|
||||
class LogEntryAdmin {
|
||||
+list_filter
|
||||
+search_fields
|
||||
+list_display
|
||||
+has_add_permission()
|
||||
+has_change_permission()
|
||||
+has_delete_permission()
|
||||
+object_link()
|
||||
+user_link()
|
||||
+get_queryset()
|
||||
+get_actions()
|
||||
}
|
||||
|
||||
%% 站点地图类
|
||||
class StaticViewSitemap {
|
||||
+priority
|
||||
+changefreq
|
||||
+items()
|
||||
+location()
|
||||
}
|
||||
|
||||
class ArticleSiteMap {
|
||||
+changefreq
|
||||
+priority
|
||||
+items()
|
||||
+lastmod()
|
||||
}
|
||||
|
||||
class CategorySiteMap {
|
||||
+changefreq
|
||||
+priority
|
||||
+items()
|
||||
+lastmod()
|
||||
}
|
||||
|
||||
class TagSiteMap {
|
||||
+changefreq
|
||||
+priority
|
||||
+items()
|
||||
+lastmod()
|
||||
}
|
||||
|
||||
class UserSiteMap {
|
||||
+changefreq
|
||||
+priority
|
||||
+items()
|
||||
+lastmod()
|
||||
}
|
||||
|
||||
%% 蜘蛛通知类
|
||||
class SpiderNotify {
|
||||
<<static>>
|
||||
+baidu_notify()
|
||||
+notify()
|
||||
}
|
||||
|
||||
%% 测试类
|
||||
class DjangoBlogTest
|
||||
|
||||
%% 工具类
|
||||
class CommonMarkdown {
|
||||
<<static>>
|
||||
+_convert_markdown()
|
||||
+get_markdown_with_toc()
|
||||
+get_markdown()
|
||||
}
|
||||
|
||||
%% 设置类(配置)
|
||||
class settings {
|
||||
<<module>>
|
||||
+INSTALLED_APPS
|
||||
+MIDDLEWARE
|
||||
+DATABASES
|
||||
+HAYSTACK_CONNECTIONS
|
||||
+CACHES
|
||||
+LOGGING
|
||||
}
|
||||
|
||||
%% URL配置
|
||||
class urls {
|
||||
<<module>>
|
||||
+urlpatterns
|
||||
+handler404
|
||||
+handler500
|
||||
+handle403
|
||||
}
|
||||
|
||||
%% WSGI配置
|
||||
class wsgi {
|
||||
<<module>>
|
||||
+application
|
||||
}
|
||||
|
||||
%% 继承关系
|
||||
DjangoBlogAdminSite --|> AdminSite
|
||||
DjangoblogAppConfig --|> AppConfig
|
||||
ElasticSearchBackend --|> BaseSearchBackend
|
||||
ElasticSearchQuery --|> BaseSearchQuery
|
||||
ElasticSearchEngine --|> BaseEngine
|
||||
WhooshSearchBackend --|> BaseSearchBackend
|
||||
WhooshSearchQuery --|> BaseSearchQuery
|
||||
WhooshEngine --|> BaseEngine
|
||||
WhooshHtmlFormatter --|> HtmlFormatter
|
||||
DjangoBlogFeed --|> Feed
|
||||
LogEntryAdmin --|> ModelAdmin
|
||||
StaticViewSitemap --|> Sitemap
|
||||
ArticleSiteMap --|> Sitemap
|
||||
CategorySiteMap --|> Sitemap
|
||||
TagSiteMap --|> Sitemap
|
||||
UserSiteMap --|> Sitemap
|
||||
DjangoBlogTest --|> TestCase
|
||||
|
||||
%% 关联关系
|
||||
DjangoBlogAdminSite --> ArticlelAdmin : 注册
|
||||
DjangoBlogAdminSite --> CategoryAdmin : 注册
|
||||
DjangoBlogAdminSite --> TagAdmin : 注册
|
||||
DjangoBlogAdminSite --> LinksAdmin : 注册
|
||||
DjangoBlogAdminSite --> SideBarAdmin : 注册
|
||||
DjangoBlogAdminSite --> BlogSettingsAdmin : 注册
|
||||
DjangoBlogAdminSite --> CommandsAdmin : 注册
|
||||
DjangoBlogAdminSite --> EmailSendLogAdmin : 注册
|
||||
DjangoBlogAdminSite --> BlogUserAdmin : 注册
|
||||
DjangoBlogAdminSite --> CommentAdmin : 注册
|
||||
DjangoBlogAdminSite --> OAuthUserAdmin : 注册
|
||||
DjangoBlogAdminSite --> OAuthConfigAdmin : 注册
|
||||
DjangoBlogAdminSite --> OwnTrackLogsAdmin : 注册
|
||||
DjangoBlogAdminSite --> SiteAdmin : 注册
|
||||
DjangoBlogAdminSite --> LogEntryAdmin : 注册
|
||||
|
||||
ElasticSearchBackend --> ArticleDocumentManager : 使用
|
||||
ElasticSearchModelSearchForm --> ElasticSearchBackend : 配置
|
||||
WhooshSearchBackend --> ChineseAnalyzer : 使用
|
||||
|
||||
blog_signals --> Comment : 信号处理
|
||||
blog_signals --> OAuthUser : 信号处理
|
||||
blog_signals --> LogEntry : 信号处理
|
||||
blog_signals --> EmailSendLog : 记录
|
||||
|
||||
DjangoBlogFeed --> Article : 获取内容
|
||||
ArticleSiteMap --> Article : 映射
|
||||
CategorySiteMap --> Category : 映射
|
||||
TagSiteMap --> Tag : 映射
|
||||
UserSiteMap --> Article : 映射
|
||||
|
||||
%% 依赖关系
|
||||
DjangoblogAppConfig ..> plugin_manage.loader : 导入
|
||||
blog_signals ..> comments.utils : 导入
|
||||
blog_signals ..> djangoblog.utils : 导入
|
||||
blog_signals ..> djangoblog.spider_notify : 导入
|
||||
ElasticSearchBackend ..> blog.documents : 导入
|
||||
WhooshSearchBackend ..> jieba.analyse : 导入
|
||||
utils ..> bleach : 导入
|
||||
utils ..> markdown : 导入
|
||||
|
||||
%% 配置关系
|
||||
settings --> INSTALLED_APPS : 包含所有应用
|
||||
settings --> MIDDLEWARE : 包含中间件
|
||||
urls --> admin_site : 包含管理路由
|
||||
urls --> blog.urls : 包含博客路由
|
||||
urls --> comments.urls : 包含评论路由
|
||||
urls --> accounts.urls : 包含账户路由
|
||||
urls --> oauth.urls : 包含OAuth路由
|
||||
urls --> servermanager.urls : 包含服务器管理路由
|
||||
urls --> owntracks.urls : 包含位置跟踪路由
|
||||
@ -1,194 +0,0 @@
|
||||
---
|
||||
config:
|
||||
layout: elk
|
||||
---
|
||||
classDiagram
|
||||
%% Models
|
||||
class OAuthUser {
|
||||
+ForeignKey author
|
||||
+CharField openid
|
||||
+CharField nickname
|
||||
+CharField token
|
||||
+CharField picture
|
||||
+CharField type
|
||||
+CharField email
|
||||
+TextField metadata
|
||||
+DateTimeField creation_time
|
||||
+DateTimeField last_modify_time
|
||||
+__str__()
|
||||
}
|
||||
|
||||
class OAuthConfig {
|
||||
+CharField type
|
||||
+CharField appkey
|
||||
+CharField appsecret
|
||||
+CharField callback_url
|
||||
+BooleanField is_enable
|
||||
+DateTimeField creation_time
|
||||
+DateTimeField last_modify_time
|
||||
+clean()
|
||||
+__str__()
|
||||
}
|
||||
|
||||
%% Admin Classes
|
||||
class OAuthUserAdmin {
|
||||
+search_fields
|
||||
+list_per_page
|
||||
+list_display
|
||||
+list_display_links
|
||||
+list_filter
|
||||
+readonly_fields
|
||||
+get_readonly_fields()
|
||||
+has_add_permission()
|
||||
+link_to_usermodel()
|
||||
+show_user_image()
|
||||
}
|
||||
|
||||
class OAuthConfigAdmin {
|
||||
+list_display
|
||||
+list_filter
|
||||
}
|
||||
|
||||
%% Forms
|
||||
class RequireEmailForm {
|
||||
+EmailField email
|
||||
+IntegerField oauthid
|
||||
+__init__()
|
||||
}
|
||||
|
||||
%% Views
|
||||
class RequireEmailView {
|
||||
+form_class
|
||||
+template_name
|
||||
+get()
|
||||
+get_initial()
|
||||
+get_context_data()
|
||||
+form_valid()
|
||||
}
|
||||
|
||||
%% OAuth Manager Classes
|
||||
class BaseOauthManager {
|
||||
<<Abstract>>
|
||||
+AUTH_URL
|
||||
+TOKEN_URL
|
||||
+API_URL
|
||||
+ICON_NAME
|
||||
+access_token
|
||||
+openid
|
||||
+is_access_token_set
|
||||
+is_authorized
|
||||
+get_authorization_url()
|
||||
+get_access_token_by_code()
|
||||
+get_oauth_userinfo()
|
||||
+get_picture()
|
||||
+do_get()
|
||||
+do_post()
|
||||
+get_config()
|
||||
}
|
||||
|
||||
class ProxyManagerMixin {
|
||||
+proxies
|
||||
+do_get()
|
||||
+do_post()
|
||||
}
|
||||
|
||||
class WBOauthManager {
|
||||
+client_id
|
||||
+client_secret
|
||||
+callback_url
|
||||
+get_authorization_url()
|
||||
+get_access_token_by_code()
|
||||
+get_oauth_userinfo()
|
||||
+get_picture()
|
||||
}
|
||||
|
||||
class GoogleOauthManager {
|
||||
+client_id
|
||||
+client_secret
|
||||
+callback_url
|
||||
+get_authorization_url()
|
||||
+get_access_token_by_code()
|
||||
+get_oauth_userinfo()
|
||||
+get_picture()
|
||||
}
|
||||
|
||||
class GitHubOauthManager {
|
||||
+client_id
|
||||
+client_secret
|
||||
+callback_url
|
||||
+get_authorization_url()
|
||||
+get_access_token_by_code()
|
||||
+get_oauth_userinfo()
|
||||
+get_picture()
|
||||
}
|
||||
|
||||
class FaceBookOauthManager {
|
||||
+client_id
|
||||
+client_secret
|
||||
+callback_url
|
||||
+get_authorization_url()
|
||||
+get_access_token_by_code()
|
||||
+get_oauth_userinfo()
|
||||
+get_picture()
|
||||
}
|
||||
|
||||
class QQOauthManager {
|
||||
+client_id
|
||||
+client_secret
|
||||
+callback_url
|
||||
+get_authorization_url()
|
||||
+get_access_token_by_code()
|
||||
+get_open_id()
|
||||
+get_oauth_userinfo()
|
||||
+get_picture()
|
||||
}
|
||||
|
||||
%% Test Classes
|
||||
class OAuthConfigTest {
|
||||
+setUp()
|
||||
+test_oauth_login_test()
|
||||
}
|
||||
|
||||
class OauthLoginTest {
|
||||
+setUp()
|
||||
+init_apps()
|
||||
+get_app_by_type()
|
||||
+test_weibo_login()
|
||||
+test_google_login()
|
||||
+test_github_login()
|
||||
+test_facebook_login()
|
||||
+test_qq_login()
|
||||
+test_weibo_authoriz_login_with_email()
|
||||
+test_weibo_authoriz_login_without_email()
|
||||
}
|
||||
|
||||
%% Exception Class
|
||||
class OAuthAccessTokenException {
|
||||
<<Exception>>
|
||||
}
|
||||
|
||||
%% Relationships
|
||||
OAuthUserAdmin --> OAuthUser : 管理
|
||||
OAuthConfigAdmin --> OAuthConfig : 管理
|
||||
|
||||
RequireEmailView --> RequireEmailForm : 使用
|
||||
|
||||
BaseOauthManager <|-- WBOauthManager : 继承
|
||||
BaseOauthManager <|-- QQOauthManager : 继承
|
||||
|
||||
BaseOauthManager <|-- GoogleOauthManager : 继承
|
||||
ProxyManagerMixin <|.. GoogleOauthManager : 混入
|
||||
|
||||
BaseOauthManager <|-- GitHubOauthManager : 继承
|
||||
ProxyManagerMixin <|.. GitHubOauthManager : 混入
|
||||
|
||||
BaseOauthManager <|-- FaceBookOauthManager : 继承
|
||||
ProxyManagerMixin <|.. FaceBookOauthManager : 混入
|
||||
|
||||
BaseOauthManager --> OAuthUser : 创建
|
||||
BaseOauthManager --> OAuthConfig : 配置依赖
|
||||
|
||||
OAuthConfigTest --> OAuthConfig : 测试
|
||||
OauthLoginTest --> BaseOauthManager : 测试
|
||||
OauthLoginTest --> OAuthConfig : 测试
|
||||
OauthLoginTest --> OAuthUser : 测试
|
||||
@ -1,71 +0,0 @@
|
||||
---
|
||||
config:
|
||||
layout: elk
|
||||
---
|
||||
classDiagram
|
||||
%% Models
|
||||
class OwnTrackLog {
|
||||
+CharField tid
|
||||
+FloatField lat
|
||||
+FloatField lon
|
||||
+DateTimeField creation_time
|
||||
+__str__()
|
||||
}
|
||||
|
||||
%% Admin Classes
|
||||
class OwnTrackLogsAdmin {
|
||||
# 空管理类,使用默认配置
|
||||
}
|
||||
|
||||
%% App Config
|
||||
class OwntracksConfig {
|
||||
+name
|
||||
}
|
||||
|
||||
%% Test Classes
|
||||
class OwnTrackLogTest {
|
||||
+setUp()
|
||||
+test_own_track_log()
|
||||
}
|
||||
|
||||
%% Views (作为功能模块表示)
|
||||
class ViewFunctions {
|
||||
<<Module>>
|
||||
+manage_owntrack_log()
|
||||
+show_maps()
|
||||
+show_log_dates()
|
||||
+convert_to_amap()
|
||||
+get_datas()
|
||||
}
|
||||
|
||||
%% URL Patterns
|
||||
class URLConfig {
|
||||
<<Module>>
|
||||
+logtracks
|
||||
+show_maps
|
||||
+get_datas
|
||||
+show_dates
|
||||
}
|
||||
|
||||
%% External Dependencies
|
||||
class BlogUser {
|
||||
<<External>>
|
||||
+create_superuser()
|
||||
}
|
||||
|
||||
class AMapAPI {
|
||||
<<External>>
|
||||
+坐标转换服务
|
||||
}
|
||||
|
||||
%% Relationships
|
||||
OwnTrackLogsAdmin --> OwnTrackLog : 管理
|
||||
OwnTrackLogTest --> OwnTrackLog : 测试
|
||||
OwnTrackLogTest --> BlogUser : 创建测试用户
|
||||
|
||||
ViewFunctions --> OwnTrackLog : 创建/查询
|
||||
ViewFunctions --> AMapAPI : 调用坐标转换
|
||||
|
||||
URLConfig --> ViewFunctions : 路由映射
|
||||
|
||||
OwntracksConfig ..> OwnTrackLog : 应用配置
|
||||
@ -1,155 +0,0 @@
|
||||
---
|
||||
config:
|
||||
layout: elk
|
||||
---
|
||||
classDiagram
|
||||
%% Models
|
||||
class commands {
|
||||
+CharField title
|
||||
+CharField command
|
||||
+CharField describe
|
||||
+DateTimeField creation_time
|
||||
+DateTimeField last_modify_time
|
||||
+__str__()
|
||||
}
|
||||
|
||||
class EmailSendLog {
|
||||
+CharField emailto
|
||||
+CharField title
|
||||
+TextField content
|
||||
+BooleanField send_result
|
||||
+DateTimeField creation_time
|
||||
+__str__()
|
||||
}
|
||||
|
||||
%% Admin Classes
|
||||
class CommandsAdmin {
|
||||
+list_display
|
||||
}
|
||||
|
||||
class EmailSendLogAdmin {
|
||||
+list_display
|
||||
+readonly_fields
|
||||
+has_add_permission()
|
||||
}
|
||||
|
||||
%% WeChat Robot Components
|
||||
class MemcacheStorage {
|
||||
+prefix
|
||||
+cache
|
||||
+is_available
|
||||
+key_name()
|
||||
+get()
|
||||
+set()
|
||||
+delete()
|
||||
}
|
||||
|
||||
class MessageHandler {
|
||||
+message
|
||||
+session
|
||||
+userid
|
||||
+userinfo
|
||||
+is_admin
|
||||
+is_password_set
|
||||
+save_session()
|
||||
+handler()
|
||||
}
|
||||
|
||||
class WxUserInfo {
|
||||
+isAdmin
|
||||
+isPasswordSet
|
||||
+Count
|
||||
+Command
|
||||
}
|
||||
|
||||
%% External API Classes
|
||||
class BlogApi {
|
||||
<<External>>
|
||||
+search_articles()
|
||||
+get_category_lists()
|
||||
+get_recent_articles()
|
||||
}
|
||||
|
||||
class CommandHandler {
|
||||
<<External>>
|
||||
+run()
|
||||
+get_help()
|
||||
}
|
||||
|
||||
class ChatGPT {
|
||||
<<External>>
|
||||
+chat()
|
||||
}
|
||||
|
||||
class WeRoBot {
|
||||
<<External>>
|
||||
+token
|
||||
+config
|
||||
+filter()
|
||||
+handler()
|
||||
}
|
||||
|
||||
%% Test Classes
|
||||
class ServerManagerTest {
|
||||
+setUp()
|
||||
+test_chat_gpt()
|
||||
+test_validate_comment()
|
||||
}
|
||||
|
||||
%% App Config
|
||||
class ServermanagerConfig {
|
||||
+name
|
||||
}
|
||||
|
||||
%% URL Configuration
|
||||
class URLConfig {
|
||||
+robot
|
||||
}
|
||||
|
||||
%% External Models (for testing)
|
||||
class BlogUser {
|
||||
<<External>>
|
||||
+create_superuser()
|
||||
}
|
||||
|
||||
class Category {
|
||||
<<External>>
|
||||
}
|
||||
|
||||
class Article {
|
||||
<<External>>
|
||||
+title
|
||||
+body
|
||||
+get_full_url()
|
||||
}
|
||||
|
||||
%% Relationships
|
||||
CommandsAdmin --> commands : 管理
|
||||
EmailSendLogAdmin --> EmailSendLog : 管理
|
||||
|
||||
MemcacheStorage ..> WeRoBot : 会话存储实现
|
||||
MessageHandler o-- WxUserInfo : 组合
|
||||
MessageHandler --> CommandHandler : 使用
|
||||
MessageHandler --> ChatGPT : 使用
|
||||
|
||||
WeRoBot --> MessageHandler : 消息处理
|
||||
WeRoBot --> BlogApi : 博客API调用
|
||||
WeRoBot --> MemcacheStorage : 会话存储
|
||||
|
||||
%% Robot Filter Functions
|
||||
WeRoBot ..> search : 注册过滤器
|
||||
WeRoBot ..> category : 注册过滤器
|
||||
WeRoBot ..> recents : 注册过滤器
|
||||
WeRoBot ..> help : 注册过滤器
|
||||
WeRoBot ..> weather : 注册过滤器
|
||||
WeRoBot ..> idcard : 注册过滤器
|
||||
|
||||
%% Test Relationships
|
||||
ServerManagerTest --> commands : 测试
|
||||
ServerManagerTest --> MessageHandler : 测试
|
||||
ServerManagerTest --> CommandHandler : 测试
|
||||
ServerManagerTest --> BlogUser : 测试依赖
|
||||
ServerManagerTest --> Category : 测试依赖
|
||||
ServerManagerTest --> Article : 测试依赖
|
||||
|
||||
URLConfig --> WeRoBot : 路由配置
|
||||
@ -1,22 +0,0 @@
|
||||
@echo off
|
||||
chcp 65001 >nul
|
||||
echo 正在检查将要清理的文件和文件夹...
|
||||
git clean -fd -n
|
||||
|
||||
set /p confirm=是否确认清理这些文件?(y/N):
|
||||
if /i "%confirm%" neq "y" (
|
||||
echo 操作已取消
|
||||
pause
|
||||
exit /b
|
||||
)
|
||||
|
||||
echo 正在执行清理操作...
|
||||
git clean -fd
|
||||
|
||||
if %errorlevel% equ 0 (
|
||||
echo 清理完成!
|
||||
) else (
|
||||
echo 清理过程中出现错误,错误代码: %errorlevel%
|
||||
)
|
||||
|
||||
pause
|
||||
@ -1,9 +0,0 @@
|
||||
@echo off
|
||||
echo Pushing Git subtree...
|
||||
git subtree push --prefix=src/DjangoBlog DjangoBlog g3f-CodeEdit
|
||||
if %errorlevel% equ 0 (
|
||||
echo Subtree push successful!
|
||||
) else (
|
||||
echo Subtree push failed!
|
||||
)
|
||||
pause
|
||||
@ -1,12 +0,0 @@
|
||||
bin/data/
|
||||
# virtualenv
|
||||
venv/
|
||||
collectedstatic/
|
||||
djangoblog/whoosh_index/
|
||||
uploads/
|
||||
settings_production.py
|
||||
*.md
|
||||
docs/
|
||||
logs/
|
||||
static/
|
||||
.github/
|
||||
@ -1,6 +0,0 @@
|
||||
blog/static/* linguist-vendored
|
||||
*.js linguist-vendored
|
||||
*.css linguist-vendored
|
||||
* text=auto
|
||||
*.sh text eol=lf
|
||||
*.conf text eol=lf
|
||||
@ -1,18 +0,0 @@
|
||||
<!--
|
||||
如果你不认真勾选下面的内容,我可能会直接关闭你的 Issue。
|
||||
提问之前,建议先阅读 https://github.com/ruby-china/How-To-Ask-Questions-The-Smart-Way
|
||||
-->
|
||||
|
||||
**我确定我已经查看了** (标注`[ ]`为`[x]`)
|
||||
|
||||
- [ ] [DjangoBlog的readme](https://github.com/liangliangyy/DjangoBlog/blob/master/README.md)
|
||||
- [ ] [配置说明](https://github.com/liangliangyy/DjangoBlog/blob/master/bin/config.md)
|
||||
- [ ] [其他 Issues](https://github.com/liangliangyy/DjangoBlog/issues)
|
||||
|
||||
----
|
||||
|
||||
**我要申请** (标注`[ ]`为`[x]`)
|
||||
|
||||
- [ ] BUG 反馈
|
||||
- [ ] 添加新的特性或者功能
|
||||
- [ ] 请求技术支持
|
||||
@ -1,7 +0,0 @@
|
||||
version: 2
|
||||
updates:
|
||||
- package-ecosystem: "pip"
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: "weekly"
|
||||
open-pull-requests-limit: 5
|
||||
@ -1,49 +0,0 @@
|
||||
name: "CodeQL"
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
- dev
|
||||
paths-ignore:
|
||||
- '**/*.md'
|
||||
- '**/*.css'
|
||||
- '**/*.js'
|
||||
- '**/*.yml'
|
||||
- '**/*.txt'
|
||||
pull_request:
|
||||
branches:
|
||||
- master
|
||||
- dev
|
||||
paths-ignore:
|
||||
- '**/*.md'
|
||||
- '**/*.css'
|
||||
- '**/*.js'
|
||||
- '**/*.yml'
|
||||
- '**/*.txt'
|
||||
schedule:
|
||||
- cron: '30 1 * * 0'
|
||||
|
||||
|
||||
jobs:
|
||||
CodeQL-Build:
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
security-events: write
|
||||
actions: read
|
||||
contents: read
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@v3
|
||||
with:
|
||||
languages: python
|
||||
|
||||
- name: Autobuild
|
||||
uses: github/codeql-action/autobuild@v3
|
||||
|
||||
- name: Perform CodeQL Analysis
|
||||
uses: github/codeql-action/analyze@v3
|
||||
@ -1,176 +0,0 @@
|
||||
name: 自动部署到生产环境
|
||||
|
||||
on:
|
||||
workflow_run:
|
||||
workflows: ["Django CI"]
|
||||
types:
|
||||
- completed
|
||||
branches:
|
||||
- master
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
environment:
|
||||
description: '部署环境'
|
||||
required: true
|
||||
default: 'production'
|
||||
type: choice
|
||||
options:
|
||||
- production
|
||||
- staging
|
||||
image_tag:
|
||||
description: '镜像标签 (默认: latest)'
|
||||
required: false
|
||||
default: 'latest'
|
||||
type: string
|
||||
skip_tests:
|
||||
description: '跳过测试直接部署'
|
||||
required: false
|
||||
default: false
|
||||
type: boolean
|
||||
|
||||
env:
|
||||
REGISTRY: registry.cn-shenzhen.aliyuncs.com
|
||||
IMAGE_NAME: liangliangyy/djangoblog
|
||||
NAMESPACE: djangoblog
|
||||
|
||||
jobs:
|
||||
deploy:
|
||||
name: 构建镜像并部署到生产环境
|
||||
runs-on: ubuntu-latest
|
||||
if: ${{ github.event.workflow_run.conclusion == 'success' || github.event_name == 'workflow_dispatch' }}
|
||||
|
||||
steps:
|
||||
- name: 检出代码
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: 设置部署参数
|
||||
id: deploy-params
|
||||
run: |
|
||||
if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then
|
||||
echo "trigger_type=手动触发" >> $GITHUB_OUTPUT
|
||||
echo "environment=${{ github.event.inputs.environment }}" >> $GITHUB_OUTPUT
|
||||
echo "image_tag=${{ github.event.inputs.image_tag }}" >> $GITHUB_OUTPUT
|
||||
echo "skip_tests=${{ github.event.inputs.skip_tests }}" >> $GITHUB_OUTPUT
|
||||
else
|
||||
echo "trigger_type=CI自动触发" >> $GITHUB_OUTPUT
|
||||
echo "environment=production" >> $GITHUB_OUTPUT
|
||||
echo "image_tag=latest" >> $GITHUB_OUTPUT
|
||||
echo "skip_tests=false" >> $GITHUB_OUTPUT
|
||||
fi
|
||||
|
||||
- name: 显示部署信息
|
||||
run: |
|
||||
echo "🚀 部署信息:"
|
||||
echo " 触发方式: ${{ steps.deploy-params.outputs.trigger_type }}"
|
||||
echo " 部署环境: ${{ steps.deploy-params.outputs.environment }}"
|
||||
echo " 镜像标签: ${{ steps.deploy-params.outputs.image_tag }}"
|
||||
echo " 跳过测试: ${{ steps.deploy-params.outputs.skip_tests }}"
|
||||
|
||||
- name: 设置Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
|
||||
- name: 登录私有镜像仓库
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
registry: ${{ env.REGISTRY }}
|
||||
username: ${{ secrets.REGISTRY_USERNAME }}
|
||||
password: ${{ secrets.REGISTRY_PASSWORD }}
|
||||
|
||||
- name: 提取镜像元数据
|
||||
id: meta
|
||||
uses: docker/metadata-action@v5
|
||||
with:
|
||||
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
|
||||
tags: |
|
||||
type=ref,event=branch
|
||||
type=sha,prefix={{branch}}-
|
||||
type=raw,value=${{ steps.deploy-params.outputs.image_tag }}
|
||||
|
||||
- name: 构建并推送Docker镜像
|
||||
uses: docker/build-push-action@v5
|
||||
with:
|
||||
context: .
|
||||
file: ./Dockerfile
|
||||
push: true
|
||||
tags: ${{ steps.meta.outputs.tags }}
|
||||
labels: ${{ steps.meta.outputs.labels }}
|
||||
cache-from: type=gha
|
||||
cache-to: type=gha,mode=max
|
||||
platforms: linux/amd64
|
||||
|
||||
- name: 部署到生产服务器
|
||||
uses: appleboy/ssh-action@v1.0.3
|
||||
with:
|
||||
host: ${{ secrets.PRODUCTION_HOST }}
|
||||
username: ${{ secrets.PRODUCTION_USER }}
|
||||
key: ${{ secrets.PRODUCTION_SSH_KEY }}
|
||||
port: ${{ secrets.PRODUCTION_PORT || 22 }}
|
||||
script: |
|
||||
echo "🚀 开始部署 DjangoBlog..."
|
||||
|
||||
# 检查kubectl是否可用
|
||||
if ! command -v kubectl &> /dev/null; then
|
||||
echo "❌ 错误: kubectl 未安装或不在PATH中"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# 检查命名空间是否存在
|
||||
if ! kubectl get namespace ${{ env.NAMESPACE }} &> /dev/null; then
|
||||
echo "❌ 错误: 命名空间 ${{ env.NAMESPACE }} 不存在"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# 更新deployment镜像
|
||||
echo "📦 更新deployment镜像为: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.deploy-params.outputs.image_tag }}"
|
||||
kubectl set image deployment/djangoblog \
|
||||
djangoblog=${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.deploy-params.outputs.image_tag }} \
|
||||
-n ${{ env.NAMESPACE }}
|
||||
|
||||
# 重启deployment
|
||||
echo "🔄 重启deployment..."
|
||||
kubectl -n ${{ env.NAMESPACE }} rollout restart deployment djangoblog
|
||||
|
||||
# 等待deployment完成
|
||||
echo "⏳ 等待deployment完成..."
|
||||
kubectl rollout status deployment/djangoblog -n ${{ env.NAMESPACE }} --timeout=300s
|
||||
|
||||
# 检查deployment状态
|
||||
echo "✅ 检查deployment状态..."
|
||||
kubectl get deployment djangoblog -n ${{ env.NAMESPACE }}
|
||||
kubectl get pods -l app=djangoblog -n ${{ env.NAMESPACE }}
|
||||
|
||||
echo "🎉 部署完成!"
|
||||
|
||||
- name: 发送部署通知
|
||||
if: always()
|
||||
run: |
|
||||
# 设置通知内容
|
||||
if [ "${{ job.status }}" = "success" ]; then
|
||||
TITLE="✅ DjangoBlog部署成功"
|
||||
STATUS="成功"
|
||||
else
|
||||
TITLE="❌ DjangoBlog部署失败"
|
||||
STATUS="失败"
|
||||
fi
|
||||
|
||||
MESSAGE="部署状态: ${STATUS}
|
||||
触发方式: ${{ steps.deploy-params.outputs.trigger_type }}
|
||||
部署环境: ${{ steps.deploy-params.outputs.environment }}
|
||||
镜像标签: ${{ steps.deploy-params.outputs.image_tag }}
|
||||
提交者: ${{ github.actor }}
|
||||
时间: $(date '+%Y-%m-%d %H:%M:%S')
|
||||
|
||||
查看详情: https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}"
|
||||
|
||||
# 发送Server酱通知
|
||||
if [ -n "${{ secrets.SERVERCHAN_KEY }}" ]; then
|
||||
echo "{\"title\": \"${TITLE}\", \"desp\": \"${MESSAGE}\"}" > /tmp/serverchan.json
|
||||
|
||||
curl --location "https://sctapi.ftqq.com/${{ secrets.SERVERCHAN_KEY }}.send" \
|
||||
--header "Content-Type: application/json" \
|
||||
--data @/tmp/serverchan.json \
|
||||
--silent > /dev/null
|
||||
|
||||
rm -f /tmp/serverchan.json
|
||||
echo "📱 部署通知已发送"
|
||||
fi
|
||||
@ -1,371 +0,0 @@
|
||||
name: Django CI
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
- dev
|
||||
paths-ignore:
|
||||
- '**/*.md'
|
||||
- '**/*.css'
|
||||
- '**/*.js'
|
||||
pull_request:
|
||||
branches:
|
||||
- master
|
||||
- dev
|
||||
paths-ignore:
|
||||
- '**/*.md'
|
||||
- '**/*.css'
|
||||
- '**/*.js'
|
||||
|
||||
jobs:
|
||||
test:
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
# 标准测试 - Python 3.10
|
||||
- python-version: "3.10"
|
||||
test-type: "standard"
|
||||
database: "mysql"
|
||||
elasticsearch: false
|
||||
coverage: false
|
||||
|
||||
# 标准测试 - Python 3.11
|
||||
- python-version: "3.11"
|
||||
test-type: "standard"
|
||||
database: "mysql"
|
||||
elasticsearch: false
|
||||
coverage: false
|
||||
|
||||
# 完整测试 - 包含ES和覆盖率
|
||||
- python-version: "3.11"
|
||||
test-type: "full"
|
||||
database: "mysql"
|
||||
elasticsearch: true
|
||||
coverage: true
|
||||
|
||||
# Docker构建测试
|
||||
- python-version: "3.11"
|
||||
test-type: "docker"
|
||||
database: "none"
|
||||
elasticsearch: false
|
||||
coverage: false
|
||||
|
||||
name: Test (${{ matrix.test-type }}, Python ${{ matrix.python-version }})
|
||||
|
||||
steps:
|
||||
- name: Checkout代码
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: 设置测试信息
|
||||
id: test-info
|
||||
run: |
|
||||
echo "test_name=${{ matrix.test-type }}-py${{ matrix.python-version }}" >> $GITHUB_OUTPUT
|
||||
if [ "${{ matrix.test-type }}" = "docker" ]; then
|
||||
echo "skip_python_setup=true" >> $GITHUB_OUTPUT
|
||||
else
|
||||
echo "skip_python_setup=false" >> $GITHUB_OUTPUT
|
||||
fi
|
||||
|
||||
# MySQL数据库设置 (只有需要数据库的测试才执行)
|
||||
- name: 启动MySQL数据库
|
||||
if: matrix.database == 'mysql'
|
||||
uses: samin/mysql-action@v1.3
|
||||
with:
|
||||
host port: 3306
|
||||
container port: 3306
|
||||
character set server: utf8mb4
|
||||
collation server: utf8mb4_general_ci
|
||||
mysql version: latest
|
||||
mysql root password: root
|
||||
mysql database: djangoblog
|
||||
mysql user: root
|
||||
mysql password: root
|
||||
|
||||
# Elasticsearch设置 (只有完整测试才执行)
|
||||
- name: 配置系统参数 (ES)
|
||||
if: matrix.elasticsearch == true
|
||||
run: |
|
||||
sudo swapoff -a
|
||||
sudo sysctl -w vm.swappiness=1
|
||||
sudo sysctl -w fs.file-max=262144
|
||||
sudo sysctl -w vm.max_map_count=262144
|
||||
|
||||
- name: 启动Elasticsearch
|
||||
if: matrix.elasticsearch == true
|
||||
uses: miyataka/elasticsearch-github-actions@1
|
||||
with:
|
||||
stack-version: '7.12.1'
|
||||
plugins: 'https://release.infinilabs.com/analysis-ik/stable/elasticsearch-analysis-ik-7.12.1.zip'
|
||||
|
||||
# Python环境设置 (Docker测试跳过)
|
||||
- name: 设置Python ${{ matrix.python-version }}
|
||||
if: steps.test-info.outputs.skip_python_setup == 'false'
|
||||
uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: ${{ matrix.python-version }}
|
||||
cache: 'pip'
|
||||
cache-dependency-path: 'requirements.txt'
|
||||
|
||||
# 多层缓存策略优化
|
||||
- name: 缓存Python依赖
|
||||
if: steps.test-info.outputs.skip_python_setup == 'false'
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: |
|
||||
~/.cache/pip
|
||||
.pytest_cache
|
||||
key: ${{ runner.os }}-python-${{ matrix.python-version }}-${{ hashFiles('requirements.txt') }}-${{ hashFiles('**/pyproject.toml') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-python-${{ matrix.python-version }}-${{ hashFiles('requirements.txt') }}-
|
||||
${{ runner.os }}-python-${{ matrix.python-version }}-
|
||||
${{ runner.os }}-python-
|
||||
|
||||
# Django缓存优化 (测试数据库等)
|
||||
- name: 缓存Django资源
|
||||
if: matrix.test-type != 'docker'
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: |
|
||||
.coverage*
|
||||
htmlcov/
|
||||
.django_cache/
|
||||
key: ${{ runner.os }}-django-${{ matrix.test-type }}-${{ github.sha }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-django-${{ matrix.test-type }}-
|
||||
${{ runner.os }}-django-
|
||||
|
||||
- name: 安装Python依赖
|
||||
if: steps.test-info.outputs.skip_python_setup == 'false'
|
||||
run: |
|
||||
echo "📦 安装Python依赖 (Python ${{ matrix.python-version }})"
|
||||
python -m pip install --upgrade pip setuptools wheel
|
||||
|
||||
# 安装基础依赖
|
||||
pip install -r requirements.txt
|
||||
|
||||
# 根据测试类型安装额外依赖
|
||||
if [ "${{ matrix.coverage }}" = "true" ]; then
|
||||
echo "📊 安装覆盖率工具"
|
||||
pip install coverage[toml]
|
||||
fi
|
||||
|
||||
# 验证关键依赖
|
||||
echo "🔍 验证关键依赖安装"
|
||||
python -c "import django; print(f'Django version: {django.get_version()}')"
|
||||
python -c "import MySQLdb; print('MySQL client: OK')" || python -c "import pymysql; print('PyMySQL client: OK')"
|
||||
|
||||
if [ "${{ matrix.elasticsearch }}" = "true" ]; then
|
||||
python -c "import elasticsearch; print('Elasticsearch client: OK')"
|
||||
fi
|
||||
|
||||
# Django环境准备
|
||||
- name: 准备Django环境
|
||||
if: matrix.test-type != 'docker'
|
||||
env:
|
||||
DJANGO_MYSQL_PASSWORD: root
|
||||
DJANGO_MYSQL_HOST: 127.0.0.1
|
||||
DJANGO_ELASTICSEARCH_HOST: ${{ matrix.elasticsearch && '127.0.0.1:9200' || '' }}
|
||||
run: |
|
||||
echo "🔧 准备Django测试环境"
|
||||
|
||||
# 等待数据库就绪
|
||||
echo "⏳ 等待MySQL数据库启动..."
|
||||
for i in {1..30}; do
|
||||
if python -c "import MySQLdb; MySQLdb.connect(host='127.0.0.1', user='root', passwd='root', db='djangoblog')" 2>/dev/null; then
|
||||
echo "✅ MySQL数据库连接成功"
|
||||
break
|
||||
fi
|
||||
echo "🔄 等待数据库启动... ($i/30)"
|
||||
sleep 2
|
||||
done
|
||||
|
||||
# 等待Elasticsearch就绪 (如果启用)
|
||||
if [ "${{ matrix.elasticsearch }}" = "true" ]; then
|
||||
echo "⏳ 等待Elasticsearch启动..."
|
||||
for i in {1..30}; do
|
||||
if curl -s http://127.0.0.1:9200/_cluster/health | grep -q '"status":"green"\|"status":"yellow"'; then
|
||||
echo "✅ Elasticsearch连接成功"
|
||||
break
|
||||
fi
|
||||
echo "🔄 等待Elasticsearch启动... ($i/30)"
|
||||
sleep 2
|
||||
done
|
||||
fi
|
||||
|
||||
# Django测试执行
|
||||
- name: 执行数据库迁移
|
||||
if: matrix.test-type != 'docker'
|
||||
env:
|
||||
DJANGO_MYSQL_PASSWORD: root
|
||||
DJANGO_MYSQL_HOST: 127.0.0.1
|
||||
DJANGO_ELASTICSEARCH_HOST: ${{ matrix.elasticsearch && '127.0.0.1:9200' || '' }}
|
||||
run: |
|
||||
echo "🗄️ 执行数据库迁移"
|
||||
|
||||
# 检查迁移文件
|
||||
echo "📋 检查待应用的迁移..."
|
||||
python manage.py showmigrations
|
||||
|
||||
# 检查是否有未创建的迁移
|
||||
python manage.py makemigrations --check --verbosity 2
|
||||
|
||||
# 执行迁移
|
||||
python manage.py migrate --verbosity 2
|
||||
|
||||
echo "✅ 数据库迁移完成"
|
||||
|
||||
- name: 运行Django测试
|
||||
if: matrix.test-type != 'docker'
|
||||
env:
|
||||
DJANGO_MYSQL_PASSWORD: root
|
||||
DJANGO_MYSQL_HOST: 127.0.0.1
|
||||
DJANGO_ELASTICSEARCH_HOST: ${{ matrix.elasticsearch && '127.0.0.1:9200' || '' }}
|
||||
run: |
|
||||
echo "🧪 开始执行 ${{ matrix.test-type }} 测试 (Python ${{ matrix.python-version }})"
|
||||
|
||||
# 显示Django配置信息
|
||||
python manage.py diffsettings | head -20
|
||||
|
||||
# 运行测试
|
||||
if [ "${{ matrix.coverage }}" = "true" ]; then
|
||||
echo "📊 运行测试并生成覆盖率报告"
|
||||
coverage run --source='.' --omit='*/venv/*,*/migrations/*,*/tests/*,manage.py' manage.py test --verbosity=2
|
||||
|
||||
echo "📈 生成覆盖率报告"
|
||||
coverage xml
|
||||
coverage report --show-missing
|
||||
coverage html
|
||||
|
||||
echo "📋 覆盖率统计:"
|
||||
coverage report | tail -1
|
||||
else
|
||||
echo "🧪 运行标准测试"
|
||||
python manage.py test --verbosity=2 --failfast
|
||||
fi
|
||||
|
||||
echo "✅ 测试执行完成"
|
||||
|
||||
# 覆盖率报告上传 (只有完整测试才执行)
|
||||
- name: 上传覆盖率到Codecov
|
||||
if: matrix.coverage == true && success()
|
||||
uses: codecov/codecov-action@v4
|
||||
with:
|
||||
token: ${{ secrets.CODECOV_TOKEN }}
|
||||
file: ./coverage.xml
|
||||
flags: unittests
|
||||
name: codecov-${{ steps.test-info.outputs.test_name }}
|
||||
fail_ci_if_error: false
|
||||
verbose: true
|
||||
|
||||
- name: 上传覆盖率到Codecov (备用)
|
||||
if: matrix.coverage == true && failure()
|
||||
uses: codecov/codecov-action@v4
|
||||
with:
|
||||
file: ./coverage.xml
|
||||
flags: unittests
|
||||
name: codecov-${{ steps.test-info.outputs.test_name }}-fallback
|
||||
fail_ci_if_error: false
|
||||
verbose: true
|
||||
|
||||
# Docker构建测试
|
||||
- name: 设置QEMU
|
||||
if: matrix.test-type == 'docker'
|
||||
uses: docker/setup-qemu-action@v3
|
||||
|
||||
- name: 设置Docker Buildx
|
||||
if: matrix.test-type == 'docker'
|
||||
uses: docker/setup-buildx-action@v3
|
||||
|
||||
- name: Docker构建测试
|
||||
if: matrix.test-type == 'docker'
|
||||
uses: docker/build-push-action@v5
|
||||
with:
|
||||
context: .
|
||||
push: false
|
||||
tags: djangoblog/djangoblog:test-${{ github.sha }}
|
||||
cache-from: type=gha
|
||||
cache-to: type=gha,mode=max
|
||||
|
||||
# 收集测试工件 (失败时收集调试信息)
|
||||
- name: 收集测试工件
|
||||
if: failure() && matrix.test-type != 'docker'
|
||||
run: |
|
||||
echo "🔍 收集测试失败的调试信息"
|
||||
|
||||
# 收集Django日志
|
||||
if [ -d "logs" ]; then
|
||||
echo "📄 Django日志文件:"
|
||||
ls -la logs/
|
||||
if [ -f "logs/djangoblog.log" ]; then
|
||||
echo "🔍 最新日志内容:"
|
||||
tail -100 logs/djangoblog.log
|
||||
fi
|
||||
fi
|
||||
|
||||
# 显示数据库状态
|
||||
echo "🗄️ 数据库连接状态:"
|
||||
python -c "
|
||||
try:
|
||||
from django.db import connection
|
||||
cursor = connection.cursor()
|
||||
cursor.execute('SELECT VERSION()')
|
||||
print(f'MySQL版本: {cursor.fetchone()[0]}')
|
||||
cursor.execute('SHOW TABLES')
|
||||
tables = cursor.fetchall()
|
||||
print(f'数据库表数量: {len(tables)}')
|
||||
except Exception as e:
|
||||
print(f'数据库连接错误: {e}')
|
||||
" || true
|
||||
|
||||
# Elasticsearch状态 (如果启用)
|
||||
if [ "${{ matrix.elasticsearch }}" = "true" ]; then
|
||||
echo "🔍 Elasticsearch状态:"
|
||||
curl -s http://127.0.0.1:9200/_cluster/health?pretty || true
|
||||
fi
|
||||
|
||||
# 上传测试工件
|
||||
- name: 上传覆盖率HTML报告
|
||||
if: matrix.coverage == true && always()
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: coverage-report-${{ steps.test-info.outputs.test_name }}
|
||||
path: htmlcov/
|
||||
retention-days: 30
|
||||
|
||||
# 性能统计
|
||||
- name: 测试性能统计
|
||||
if: always() && matrix.test-type != 'docker'
|
||||
run: |
|
||||
echo "⚡ 测试性能统计:"
|
||||
echo " 开始时间: $(date -d '@${{ job.started_at }}' '+%Y-%m-%d %H:%M:%S' 2>/dev/null || echo '未知')"
|
||||
echo " 当前时间: $(date '+%Y-%m-%d %H:%M:%S')"
|
||||
|
||||
# 系统资源使用情况
|
||||
echo "💻 系统资源:"
|
||||
echo " CPU使用: $(top -bn1 | grep "Cpu(s)" | awk '{print $2}' | cut -d'%' -f1)%"
|
||||
echo " 内存使用: $(free -h | awk '/^Mem:/ {printf "%.1f%%", $3/$2 * 100}')"
|
||||
echo " 磁盘使用: $(df -h / | awk 'NR==2{printf "%s", $5}')"
|
||||
|
||||
# 测试结果汇总
|
||||
- name: 测试完成总结
|
||||
if: always()
|
||||
run: |
|
||||
echo "📋 ============ 测试执行总结 ============"
|
||||
echo " 🏷️ 测试类型: ${{ matrix.test-type }}"
|
||||
echo " 🐍 Python版本: ${{ matrix.python-version }}"
|
||||
echo " 🗄️ 数据库: ${{ matrix.database }}"
|
||||
echo " 🔍 Elasticsearch: ${{ matrix.elasticsearch }}"
|
||||
echo " 📊 覆盖率: ${{ matrix.coverage }}"
|
||||
echo " ⚡ 状态: ${{ job.status }}"
|
||||
echo " 📅 完成时间: $(date '+%Y-%m-%d %H:%M:%S')"
|
||||
echo "============================================"
|
||||
|
||||
# 根据测试结果显示不同消息
|
||||
if [ "${{ job.status }}" = "success" ]; then
|
||||
echo "🎉 测试执行成功!"
|
||||
else
|
||||
echo "❌ 测试执行失败,请检查上面的日志"
|
||||
fi
|
||||
@ -1,43 +0,0 @@
|
||||
name: docker
|
||||
|
||||
on:
|
||||
push:
|
||||
paths-ignore:
|
||||
- '**/*.md'
|
||||
- '**/*.yml'
|
||||
branches:
|
||||
- 'master'
|
||||
- 'dev'
|
||||
workflow_dispatch:
|
||||
jobs:
|
||||
docker:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Set env to docker dev tag
|
||||
if: endsWith(github.ref, '/dev')
|
||||
run: |
|
||||
echo "DOCKER_TAG=test" >> $GITHUB_ENV
|
||||
- name: Set env to docker latest tag
|
||||
if: endsWith(github.ref, '/master')
|
||||
run: |
|
||||
echo "DOCKER_TAG=latest" >> $GITHUB_ENV
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v3
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
|
||||
- name: Login to DockerHub
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
- name: Build and push
|
||||
uses: docker/build-push-action@v5
|
||||
with:
|
||||
context: .
|
||||
push: true
|
||||
tags: ${{ secrets.DOCKERHUB_USERNAME }}/djangoblog:${{env.DOCKER_TAG}}
|
||||
|
||||
|
||||
@ -1,39 +0,0 @@
|
||||
name: publish release
|
||||
|
||||
on:
|
||||
release:
|
||||
types: [ published ]
|
||||
|
||||
jobs:
|
||||
docker:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Docker meta
|
||||
id: meta
|
||||
uses: docker/metadata-action@v3
|
||||
with:
|
||||
images: name/app
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v2
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v2
|
||||
- name: Login to DockerHub
|
||||
uses: docker/login-action@v2
|
||||
with:
|
||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
- name: Build and push
|
||||
uses: docker/build-push-action@v3
|
||||
with:
|
||||
context: .
|
||||
push: true
|
||||
platforms: |
|
||||
linux/amd64
|
||||
linux/arm64
|
||||
linux/arm/v7
|
||||
linux/arm/v6
|
||||
linux/386
|
||||
tags: ${{ secrets.DOCKERHUB_USERNAME }}/djangoblog:${{ github.event.release.tag_name }}
|
||||
@ -1,84 +0,0 @@
|
||||
# Byte-compiled / optimized / DLL files
|
||||
__pycache__/
|
||||
*.py[cod]
|
||||
*$py.class
|
||||
|
||||
# C extensions
|
||||
*.so
|
||||
|
||||
# Distribution / packaging
|
||||
.Python
|
||||
env/
|
||||
build/
|
||||
develop-eggs/
|
||||
dist/
|
||||
downloads/
|
||||
eggs/
|
||||
.eggs/
|
||||
lib/
|
||||
lib64/
|
||||
parts/
|
||||
sdist/
|
||||
var/
|
||||
*.egg-info/
|
||||
.installed.cfg
|
||||
*.egg
|
||||
|
||||
# PyInstaller
|
||||
# Usually these files are written by a python script from a template
|
||||
# before PyInstaller builds the exe, so as to inject date/other infos into it.
|
||||
*.manifest
|
||||
*.spec
|
||||
|
||||
# Installer logs
|
||||
pip-log.txt
|
||||
pip-delete-this-directory.txt
|
||||
|
||||
# Unit test / coverage reports
|
||||
htmlcov/
|
||||
.tox/
|
||||
.coverage
|
||||
.coverage.*
|
||||
.cache
|
||||
nosetests.xml
|
||||
coverage.xml
|
||||
*,cover
|
||||
|
||||
# Translations
|
||||
*.pot
|
||||
|
||||
# Django stuff:
|
||||
*.log
|
||||
logs/
|
||||
|
||||
# Sphinx documentation
|
||||
docs/_build/
|
||||
|
||||
# PyBuilder
|
||||
target/
|
||||
|
||||
|
||||
# PyCharm
|
||||
# http://www.jetbrains.com/pycharm/webhelp/project.html
|
||||
.idea
|
||||
.iml
|
||||
# virtualenv
|
||||
venv/
|
||||
|
||||
collectedstatic/
|
||||
djangoblog/whoosh_index/
|
||||
google93fd32dbd906620a.html
|
||||
baidu_verify_FlHL7cUyC9.html
|
||||
BingSiteAuth.xml
|
||||
cb9339dbe2ff86a5aa169d28dba5f615.txt
|
||||
werobot_session.*
|
||||
django.jpg
|
||||
uploads/
|
||||
settings_production.py
|
||||
werobot_session.db
|
||||
bin/datas/
|
||||
|
||||
.env
|
||||
|
||||
# 目前似乎仅有测试代码会涉及到修改此文件夹,所以暂不进行版本管理
|
||||
static/avatar/
|
||||
@ -1,15 +0,0 @@
|
||||
FROM python:3.11
|
||||
ENV PYTHONUNBUFFERED 1
|
||||
WORKDIR /code/djangoblog/
|
||||
RUN apt-get update && \
|
||||
apt-get install default-libmysqlclient-dev gettext -y && \
|
||||
rm -rf /var/lib/apt/lists/*
|
||||
ADD requirements.txt requirements.txt
|
||||
RUN pip install --upgrade pip && \
|
||||
pip install --no-cache-dir -r requirements.txt && \
|
||||
pip install --no-cache-dir gunicorn[gevent] && \
|
||||
pip cache purge
|
||||
|
||||
ADD . .
|
||||
RUN chmod +x /code/djangoblog/deploy/entrypoint.sh
|
||||
ENTRYPOINT ["/code/djangoblog/deploy/entrypoint.sh"]
|
||||
@ -1,20 +0,0 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2025 车亮亮
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
this software and associated documentation files (the "Software"), to deal in
|
||||
the Software without restriction, including without limitation the rights to
|
||||
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||
the Software, and to permit persons to whom the Software is furnished to do so,
|
||||
subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
@ -1,11 +0,0 @@
|
||||
#shw 导入Django的应用配置基类
|
||||
from django.apps import AppConfig
|
||||
|
||||
|
||||
class AccountsConfig(AppConfig):
|
||||
#shw 这是accounts应用的配置类。
|
||||
#shw 它用于定义该应用的各种元数据和行为。
|
||||
|
||||
#shw 指定这个配置类所属的应用的完整Python路径。
|
||||
#shw Django通过这个name来找到并加载这个应用。
|
||||
name = 'accounts'
|
||||
@ -1,49 +0,0 @@
|
||||
# Generated by Django 4.1.7 on 2023-03-02 07:14
|
||||
|
||||
import django.contrib.auth.models
|
||||
import django.contrib.auth.validators
|
||||
from django.db import migrations, models
|
||||
import django.utils.timezone
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
initial = True
|
||||
|
||||
dependencies = [
|
||||
('auth', '0012_alter_user_first_name_max_length'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='BlogUser',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('password', models.CharField(max_length=128, verbose_name='password')),
|
||||
('last_login', models.DateTimeField(blank=True, null=True, verbose_name='last login')),
|
||||
('is_superuser', models.BooleanField(default=False, help_text='Designates that this user has all permissions without explicitly assigning them.', verbose_name='superuser status')),
|
||||
('username', models.CharField(error_messages={'unique': 'A user with that username already exists.'}, help_text='Required. 150 characters or fewer. Letters, digits and @/./+/-/_ only.', max_length=150, unique=True, validators=[django.contrib.auth.validators.UnicodeUsernameValidator()], verbose_name='username')),
|
||||
('first_name', models.CharField(blank=True, max_length=150, verbose_name='first name')),
|
||||
('last_name', models.CharField(blank=True, max_length=150, verbose_name='last name')),
|
||||
('email', models.EmailField(blank=True, max_length=254, verbose_name='email address')),
|
||||
('is_staff', models.BooleanField(default=False, help_text='Designates whether the user can log into this admin site.', verbose_name='staff status')),
|
||||
('is_active', models.BooleanField(default=True, help_text='Designates whether this user should be treated as active. Unselect this instead of deleting accounts.', verbose_name='active')),
|
||||
('date_joined', models.DateTimeField(default=django.utils.timezone.now, verbose_name='date joined')),
|
||||
('nickname', models.CharField(blank=True, max_length=100, verbose_name='昵称')),
|
||||
('created_time', models.DateTimeField(default=django.utils.timezone.now, verbose_name='创建时间')),
|
||||
('last_mod_time', models.DateTimeField(default=django.utils.timezone.now, verbose_name='修改时间')),
|
||||
('source', models.CharField(blank=True, max_length=100, verbose_name='创建来源')),
|
||||
('groups', models.ManyToManyField(blank=True, help_text='The groups this user belongs to. A user will get all permissions granted to each of their groups.', related_name='user_set', related_query_name='user', to='auth.group', verbose_name='groups')),
|
||||
('user_permissions', models.ManyToManyField(blank=True, help_text='Specific permissions for this user.', related_name='user_set', related_query_name='user', to='auth.permission', verbose_name='user permissions')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': '用户',
|
||||
'verbose_name_plural': '用户',
|
||||
'ordering': ['-id'],
|
||||
'get_latest_by': 'id',
|
||||
},
|
||||
managers=[
|
||||
('objects', django.contrib.auth.models.UserManager()),
|
||||
],
|
||||
),
|
||||
]
|
||||
@ -1,46 +0,0 @@
|
||||
# Generated by Django 4.2.5 on 2023-09-06 13:13
|
||||
|
||||
from django.db import migrations, models
|
||||
import django.utils.timezone
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('accounts', '0001_initial'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterModelOptions(
|
||||
name='bloguser',
|
||||
options={'get_latest_by': 'id', 'ordering': ['-id'], 'verbose_name': 'user', 'verbose_name_plural': 'user'},
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name='bloguser',
|
||||
name='created_time',
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name='bloguser',
|
||||
name='last_mod_time',
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='bloguser',
|
||||
name='creation_time',
|
||||
field=models.DateTimeField(default=django.utils.timezone.now, verbose_name='creation time'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='bloguser',
|
||||
name='last_modify_time',
|
||||
field=models.DateTimeField(default=django.utils.timezone.now, verbose_name='last modify time'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='bloguser',
|
||||
name='nickname',
|
||||
field=models.CharField(blank=True, max_length=100, verbose_name='nick name'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='bloguser',
|
||||
name='source',
|
||||
field=models.CharField(blank=True, max_length=100, verbose_name='create source'),
|
||||
),
|
||||
]
|
||||
@ -1,137 +0,0 @@
|
||||
# Generated by Django 4.1.7 on 2023-03-02 07:14
|
||||
|
||||
from django.conf import settings
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
import django.utils.timezone
|
||||
import mdeditor.fields
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
initial = True
|
||||
|
||||
dependencies = [
|
||||
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='BlogSettings',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('sitename', models.CharField(default='', max_length=200, verbose_name='网站名称')),
|
||||
('site_description', models.TextField(default='', max_length=1000, verbose_name='网站描述')),
|
||||
('site_seo_description', models.TextField(default='', max_length=1000, verbose_name='网站SEO描述')),
|
||||
('site_keywords', models.TextField(default='', max_length=1000, verbose_name='网站关键字')),
|
||||
('article_sub_length', models.IntegerField(default=300, verbose_name='文章摘要长度')),
|
||||
('sidebar_article_count', models.IntegerField(default=10, verbose_name='侧边栏文章数目')),
|
||||
('sidebar_comment_count', models.IntegerField(default=5, verbose_name='侧边栏评论数目')),
|
||||
('article_comment_count', models.IntegerField(default=5, verbose_name='文章页面默认显示评论数目')),
|
||||
('show_google_adsense', models.BooleanField(default=False, verbose_name='是否显示谷歌广告')),
|
||||
('google_adsense_codes', models.TextField(blank=True, default='', max_length=2000, null=True, verbose_name='广告内容')),
|
||||
('open_site_comment', models.BooleanField(default=True, verbose_name='是否打开网站评论功能')),
|
||||
('beiancode', models.CharField(blank=True, default='', max_length=2000, null=True, verbose_name='备案号')),
|
||||
('analyticscode', models.TextField(default='', max_length=1000, verbose_name='网站统计代码')),
|
||||
('show_gongan_code', models.BooleanField(default=False, verbose_name='是否显示公安备案号')),
|
||||
('gongan_beiancode', models.TextField(blank=True, default='', max_length=2000, null=True, verbose_name='公安备案号')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': '网站配置',
|
||||
'verbose_name_plural': '网站配置',
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='Links',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('name', models.CharField(max_length=30, unique=True, verbose_name='链接名称')),
|
||||
('link', models.URLField(verbose_name='链接地址')),
|
||||
('sequence', models.IntegerField(unique=True, verbose_name='排序')),
|
||||
('is_enable', models.BooleanField(default=True, verbose_name='是否显示')),
|
||||
('show_type', models.CharField(choices=[('i', '首页'), ('l', '列表页'), ('p', '文章页面'), ('a', '全站'), ('s', '友情链接页面')], default='i', max_length=1, verbose_name='显示类型')),
|
||||
('created_time', models.DateTimeField(default=django.utils.timezone.now, verbose_name='创建时间')),
|
||||
('last_mod_time', models.DateTimeField(default=django.utils.timezone.now, verbose_name='修改时间')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': '友情链接',
|
||||
'verbose_name_plural': '友情链接',
|
||||
'ordering': ['sequence'],
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='SideBar',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('name', models.CharField(max_length=100, verbose_name='标题')),
|
||||
('content', models.TextField(verbose_name='内容')),
|
||||
('sequence', models.IntegerField(unique=True, verbose_name='排序')),
|
||||
('is_enable', models.BooleanField(default=True, verbose_name='是否启用')),
|
||||
('created_time', models.DateTimeField(default=django.utils.timezone.now, verbose_name='创建时间')),
|
||||
('last_mod_time', models.DateTimeField(default=django.utils.timezone.now, verbose_name='修改时间')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': '侧边栏',
|
||||
'verbose_name_plural': '侧边栏',
|
||||
'ordering': ['sequence'],
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='Tag',
|
||||
fields=[
|
||||
('id', models.AutoField(primary_key=True, serialize=False)),
|
||||
('created_time', models.DateTimeField(default=django.utils.timezone.now, verbose_name='创建时间')),
|
||||
('last_mod_time', models.DateTimeField(default=django.utils.timezone.now, verbose_name='修改时间')),
|
||||
('name', models.CharField(max_length=30, unique=True, verbose_name='标签名')),
|
||||
('slug', models.SlugField(blank=True, default='no-slug', max_length=60)),
|
||||
],
|
||||
options={
|
||||
'verbose_name': '标签',
|
||||
'verbose_name_plural': '标签',
|
||||
'ordering': ['name'],
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='Category',
|
||||
fields=[
|
||||
('id', models.AutoField(primary_key=True, serialize=False)),
|
||||
('created_time', models.DateTimeField(default=django.utils.timezone.now, verbose_name='创建时间')),
|
||||
('last_mod_time', models.DateTimeField(default=django.utils.timezone.now, verbose_name='修改时间')),
|
||||
('name', models.CharField(max_length=30, unique=True, verbose_name='分类名')),
|
||||
('slug', models.SlugField(blank=True, default='no-slug', max_length=60)),
|
||||
('index', models.IntegerField(default=0, verbose_name='权重排序-越大越靠前')),
|
||||
('parent_category', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='blog.category', verbose_name='父级分类')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': '分类',
|
||||
'verbose_name_plural': '分类',
|
||||
'ordering': ['-index'],
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='Article',
|
||||
fields=[
|
||||
('id', models.AutoField(primary_key=True, serialize=False)),
|
||||
('created_time', models.DateTimeField(default=django.utils.timezone.now, verbose_name='创建时间')),
|
||||
('last_mod_time', models.DateTimeField(default=django.utils.timezone.now, verbose_name='修改时间')),
|
||||
('title', models.CharField(max_length=200, unique=True, verbose_name='标题')),
|
||||
('body', mdeditor.fields.MDTextField(verbose_name='正文')),
|
||||
('pub_time', models.DateTimeField(default=django.utils.timezone.now, verbose_name='发布时间')),
|
||||
('status', models.CharField(choices=[('d', '草稿'), ('p', '发表')], default='p', max_length=1, verbose_name='文章状态')),
|
||||
('comment_status', models.CharField(choices=[('o', '打开'), ('c', '关闭')], default='o', max_length=1, verbose_name='评论状态')),
|
||||
('type', models.CharField(choices=[('a', '文章'), ('p', '页面')], default='a', max_length=1, verbose_name='类型')),
|
||||
('views', models.PositiveIntegerField(default=0, verbose_name='浏览量')),
|
||||
('article_order', models.IntegerField(default=0, verbose_name='排序,数字越大越靠前')),
|
||||
('show_toc', models.BooleanField(default=False, verbose_name='是否显示toc目录')),
|
||||
('author', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL, verbose_name='作者')),
|
||||
('category', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='blog.category', verbose_name='分类')),
|
||||
('tags', models.ManyToManyField(blank=True, to='blog.tag', verbose_name='标签集合')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': '文章',
|
||||
'verbose_name_plural': '文章',
|
||||
'ordering': ['-article_order', '-pub_time'],
|
||||
'get_latest_by': 'id',
|
||||
},
|
||||
),
|
||||
]
|
||||
@ -1,300 +0,0 @@
|
||||
# Generated by Django 4.2.5 on 2023-09-06 13:13
|
||||
|
||||
from django.conf import settings
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
import django.utils.timezone
|
||||
import mdeditor.fields
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||
('blog', '0004_rename_analyticscode_blogsettings_analytics_code_and_more'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterModelOptions(
|
||||
name='article',
|
||||
options={'get_latest_by': 'id', 'ordering': ['-article_order', '-pub_time'], 'verbose_name': 'article', 'verbose_name_plural': 'article'},
|
||||
),
|
||||
migrations.AlterModelOptions(
|
||||
name='category',
|
||||
options={'ordering': ['-index'], 'verbose_name': 'category', 'verbose_name_plural': 'category'},
|
||||
),
|
||||
migrations.AlterModelOptions(
|
||||
name='links',
|
||||
options={'ordering': ['sequence'], 'verbose_name': 'link', 'verbose_name_plural': 'link'},
|
||||
),
|
||||
migrations.AlterModelOptions(
|
||||
name='sidebar',
|
||||
options={'ordering': ['sequence'], 'verbose_name': 'sidebar', 'verbose_name_plural': 'sidebar'},
|
||||
),
|
||||
migrations.AlterModelOptions(
|
||||
name='tag',
|
||||
options={'ordering': ['name'], 'verbose_name': 'tag', 'verbose_name_plural': 'tag'},
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name='article',
|
||||
name='created_time',
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name='article',
|
||||
name='last_mod_time',
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name='category',
|
||||
name='created_time',
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name='category',
|
||||
name='last_mod_time',
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name='links',
|
||||
name='created_time',
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name='sidebar',
|
||||
name='created_time',
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name='tag',
|
||||
name='created_time',
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name='tag',
|
||||
name='last_mod_time',
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='article',
|
||||
name='creation_time',
|
||||
field=models.DateTimeField(default=django.utils.timezone.now, verbose_name='creation time'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='article',
|
||||
name='last_modify_time',
|
||||
field=models.DateTimeField(default=django.utils.timezone.now, verbose_name='modify time'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='category',
|
||||
name='creation_time',
|
||||
field=models.DateTimeField(default=django.utils.timezone.now, verbose_name='creation time'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='category',
|
||||
name='last_modify_time',
|
||||
field=models.DateTimeField(default=django.utils.timezone.now, verbose_name='modify time'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='links',
|
||||
name='creation_time',
|
||||
field=models.DateTimeField(default=django.utils.timezone.now, verbose_name='creation time'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='sidebar',
|
||||
name='creation_time',
|
||||
field=models.DateTimeField(default=django.utils.timezone.now, verbose_name='creation time'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='tag',
|
||||
name='creation_time',
|
||||
field=models.DateTimeField(default=django.utils.timezone.now, verbose_name='creation time'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='tag',
|
||||
name='last_modify_time',
|
||||
field=models.DateTimeField(default=django.utils.timezone.now, verbose_name='modify time'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='article',
|
||||
name='article_order',
|
||||
field=models.IntegerField(default=0, verbose_name='order'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='article',
|
||||
name='author',
|
||||
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL, verbose_name='author'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='article',
|
||||
name='body',
|
||||
field=mdeditor.fields.MDTextField(verbose_name='body'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='article',
|
||||
name='category',
|
||||
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='blog.category', verbose_name='category'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='article',
|
||||
name='comment_status',
|
||||
field=models.CharField(choices=[('o', 'Open'), ('c', 'Close')], default='o', max_length=1, verbose_name='comment status'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='article',
|
||||
name='pub_time',
|
||||
field=models.DateTimeField(default=django.utils.timezone.now, verbose_name='publish time'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='article',
|
||||
name='show_toc',
|
||||
field=models.BooleanField(default=False, verbose_name='show toc'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='article',
|
||||
name='status',
|
||||
field=models.CharField(choices=[('d', 'Draft'), ('p', 'Published')], default='p', max_length=1, verbose_name='status'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='article',
|
||||
name='tags',
|
||||
field=models.ManyToManyField(blank=True, to='blog.tag', verbose_name='tag'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='article',
|
||||
name='title',
|
||||
field=models.CharField(max_length=200, unique=True, verbose_name='title'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='article',
|
||||
name='type',
|
||||
field=models.CharField(choices=[('a', 'Article'), ('p', 'Page')], default='a', max_length=1, verbose_name='type'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='article',
|
||||
name='views',
|
||||
field=models.PositiveIntegerField(default=0, verbose_name='views'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='blogsettings',
|
||||
name='article_comment_count',
|
||||
field=models.IntegerField(default=5, verbose_name='article comment count'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='blogsettings',
|
||||
name='article_sub_length',
|
||||
field=models.IntegerField(default=300, verbose_name='article sub length'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='blogsettings',
|
||||
name='google_adsense_codes',
|
||||
field=models.TextField(blank=True, default='', max_length=2000, null=True, verbose_name='adsense code'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='blogsettings',
|
||||
name='open_site_comment',
|
||||
field=models.BooleanField(default=True, verbose_name='open site comment'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='blogsettings',
|
||||
name='show_google_adsense',
|
||||
field=models.BooleanField(default=False, verbose_name='show adsense'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='blogsettings',
|
||||
name='sidebar_article_count',
|
||||
field=models.IntegerField(default=10, verbose_name='sidebar article count'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='blogsettings',
|
||||
name='sidebar_comment_count',
|
||||
field=models.IntegerField(default=5, verbose_name='sidebar comment count'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='blogsettings',
|
||||
name='site_description',
|
||||
field=models.TextField(default='', max_length=1000, verbose_name='site description'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='blogsettings',
|
||||
name='site_keywords',
|
||||
field=models.TextField(default='', max_length=1000, verbose_name='site keywords'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='blogsettings',
|
||||
name='site_name',
|
||||
field=models.CharField(default='', max_length=200, verbose_name='site name'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='blogsettings',
|
||||
name='site_seo_description',
|
||||
field=models.TextField(default='', max_length=1000, verbose_name='site seo description'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='category',
|
||||
name='index',
|
||||
field=models.IntegerField(default=0, verbose_name='index'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='category',
|
||||
name='name',
|
||||
field=models.CharField(max_length=30, unique=True, verbose_name='category name'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='category',
|
||||
name='parent_category',
|
||||
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='blog.category', verbose_name='parent category'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='links',
|
||||
name='is_enable',
|
||||
field=models.BooleanField(default=True, verbose_name='is show'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='links',
|
||||
name='last_mod_time',
|
||||
field=models.DateTimeField(default=django.utils.timezone.now, verbose_name='modify time'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='links',
|
||||
name='link',
|
||||
field=models.URLField(verbose_name='link'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='links',
|
||||
name='name',
|
||||
field=models.CharField(max_length=30, unique=True, verbose_name='link name'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='links',
|
||||
name='sequence',
|
||||
field=models.IntegerField(unique=True, verbose_name='order'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='links',
|
||||
name='show_type',
|
||||
field=models.CharField(choices=[('i', 'index'), ('l', 'list'), ('p', 'post'), ('a', 'all'), ('s', 'slide')], default='i', max_length=1, verbose_name='show type'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='sidebar',
|
||||
name='content',
|
||||
field=models.TextField(verbose_name='content'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='sidebar',
|
||||
name='is_enable',
|
||||
field=models.BooleanField(default=True, verbose_name='is enable'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='sidebar',
|
||||
name='last_mod_time',
|
||||
field=models.DateTimeField(default=django.utils.timezone.now, verbose_name='modify time'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='sidebar',
|
||||
name='name',
|
||||
field=models.CharField(max_length=100, verbose_name='title'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='sidebar',
|
||||
name='sequence',
|
||||
field=models.IntegerField(unique=True, verbose_name='order'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='tag',
|
||||
name='name',
|
||||
field=models.CharField(max_length=30, unique=True, verbose_name='tag name'),
|
||||
),
|
||||
]
|
||||
@ -1,20 +0,0 @@
|
||||
# Generated by Django 5.2.7 on 2025-11-13 13:53
|
||||
|
||||
from django.conf import settings
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('blog', '0006_alter_blogsettings_options'),
|
||||
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='article',
|
||||
name='users_like',
|
||||
field=models.ManyToManyField(blank=True, related_name='articles_liked', to=settings.AUTH_USER_MODEL, verbose_name='点赞用户'),
|
||||
),
|
||||
]
|
||||
@ -1,13 +0,0 @@
|
||||
/*!
|
||||
* IE10 viewport hack for Surface/desktop Windows 8 bug
|
||||
* Copyright 2014-2015 Twitter, Inc.
|
||||
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
|
||||
*/
|
||||
|
||||
/*
|
||||
* See the Getting Started docs for more information:
|
||||
* http://getbootstrap.com/getting-started/#support-ie10-width
|
||||
*/
|
||||
@-ms-viewport { width: device-width; }
|
||||
@-o-viewport { width: device-width; }
|
||||
@viewport { width: device-width; }
|
||||
@ -1,58 +0,0 @@
|
||||
body {
|
||||
padding-top: 40px;
|
||||
padding-bottom: 40px;
|
||||
background-color: #fff;
|
||||
}
|
||||
|
||||
.form-signin {
|
||||
max-width: 330px;
|
||||
padding: 15px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
.form-signin-heading {
|
||||
margin: 0 0 15px;
|
||||
font-size: 18px;
|
||||
font-weight: 400;
|
||||
color: #555;
|
||||
}
|
||||
.form-signin .checkbox {
|
||||
margin-bottom: 10px;
|
||||
font-weight: normal;
|
||||
}
|
||||
.form-signin .form-control {
|
||||
position: relative;
|
||||
height: auto;
|
||||
-webkit-box-sizing: border-box;
|
||||
-moz-box-sizing: border-box;
|
||||
box-sizing: border-box;
|
||||
padding: 10px;
|
||||
font-size: 16px;
|
||||
}
|
||||
.form-signin .form-control:focus {
|
||||
z-index: 2;
|
||||
}
|
||||
.form-signin input[type="email"] {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
.form-signin input[type="password"] {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
.card {
|
||||
width: 304px;
|
||||
padding: 20px 25px 30px;
|
||||
margin: 0 auto 25px;
|
||||
background-color: #f7f7f7;
|
||||
border-radius: 2px;
|
||||
-webkit-box-shadow: 0 2px 2px rgba(0, 0, 0, .3);
|
||||
box-shadow: 0 2px 2px rgba(0, 0, 0, .3);
|
||||
}
|
||||
.card-signin {
|
||||
width: 354px;
|
||||
padding: 40px;
|
||||
}
|
||||
.card-signin .profile-img {
|
||||
display: block;
|
||||
width: 96px;
|
||||
height: 96px;
|
||||
margin: 0 auto 10px;
|
||||
}
|
||||
|
Before Width: | Height: | Size: 221 B |