ccjk 14.2.2 → 15.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +1 -1
- package/README.md +58 -341
- package/dist/cli.js +59 -0
- package/dist/cli.js.map +1 -0
- package/dist/commands/detect.js +15 -0
- package/dist/commands/detect.js.map +1 -0
- package/dist/commands/doctor.js +68 -0
- package/dist/commands/doctor.js.map +1 -0
- package/dist/commands/git-install.js +50 -0
- package/dist/commands/git-install.js.map +1 -0
- package/dist/commands/init.js +102 -0
- package/dist/commands/init.js.map +1 -0
- package/dist/commands/mcp.js +66 -0
- package/dist/commands/mcp.js.map +1 -0
- package/dist/commands/menu.js +33 -0
- package/dist/commands/menu.js.map +1 -0
- package/dist/core/detect.js +24 -0
- package/dist/core/detect.js.map +1 -0
- package/dist/core/lint.js +49 -0
- package/dist/core/lint.js.map +1 -0
- package/dist/core/mcp.js +41 -0
- package/dist/core/mcp.js.map +1 -0
- package/dist/core/paths.js +9 -0
- package/dist/core/paths.js.map +1 -0
- package/dist/core/providers.js +53 -0
- package/dist/core/providers.js.map +1 -0
- package/dist/core/settings.js +31 -0
- package/dist/core/settings.js.map +1 -0
- package/dist/core/slash-templates.js +56 -0
- package/dist/core/slash-templates.js.map +1 -0
- package/dist/core/tools.js +27 -0
- package/dist/core/tools.js.map +1 -0
- package/package.json +43 -164
- package/README.HONEST.md +0 -176
- package/README.en.md +0 -67
- package/README.ja.md +0 -67
- package/README.ko.md +0 -67
- package/README.zh-CN.md +0 -86
- package/bin/ccjk.mjs +0 -5
- package/bin/ccjk.ts +0 -222
- package/dist/chunks/agent-teams.mjs +0 -145
- package/dist/chunks/agent.mjs +0 -1439
- package/dist/chunks/agents.mjs +0 -3783
- package/dist/chunks/api-cli.mjs +0 -135
- package/dist/chunks/api-config-selector.mjs +0 -159
- package/dist/chunks/api-providers.mjs +0 -144
- package/dist/chunks/api.mjs +0 -115
- package/dist/chunks/auto-bootstrap.mjs +0 -358
- package/dist/chunks/auto-fixer.mjs +0 -95
- package/dist/chunks/auto-updater.mjs +0 -507
- package/dist/chunks/banner.mjs +0 -173
- package/dist/chunks/bash.mjs +0 -187
- package/dist/chunks/boost.mjs +0 -474
- package/dist/chunks/brain-config.mjs +0 -75
- package/dist/chunks/brain-status.mjs +0 -89
- package/dist/chunks/ccjk-agents.mjs +0 -416
- package/dist/chunks/ccjk-all.mjs +0 -1046
- package/dist/chunks/ccjk-config.mjs +0 -445
- package/dist/chunks/ccjk-hooks.mjs +0 -1074
- package/dist/chunks/ccjk-mcp.mjs +0 -763
- package/dist/chunks/ccjk-setup.mjs +0 -765
- package/dist/chunks/ccjk-skills.mjs +0 -518
- package/dist/chunks/ccr.mjs +0 -109
- package/dist/chunks/ccu.mjs +0 -40
- package/dist/chunks/check-updates.mjs +0 -117
- package/dist/chunks/claude-code-incremental-manager.mjs +0 -761
- package/dist/chunks/claude-config.mjs +0 -606
- package/dist/chunks/claude-config2.mjs +0 -62
- package/dist/chunks/claude-wrapper.mjs +0 -85
- package/dist/chunks/clavue-config.mjs +0 -1454
- package/dist/chunks/cleanup-migration.mjs +0 -20
- package/dist/chunks/cli-hook.mjs +0 -4096
- package/dist/chunks/cloud-sync.mjs +0 -29
- package/dist/chunks/code-type-resolver.mjs +0 -880
- package/dist/chunks/codex-config-switch.mjs +0 -452
- package/dist/chunks/codex-provider-manager.mjs +0 -238
- package/dist/chunks/codex-uninstaller.mjs +0 -404
- package/dist/chunks/codex.mjs +0 -2141
- package/dist/chunks/commands.mjs +0 -108
- package/dist/chunks/commands2.mjs +0 -421
- package/dist/chunks/commit.mjs +0 -140
- package/dist/chunks/completion.mjs +0 -517
- package/dist/chunks/config-consolidator.mjs +0 -172
- package/dist/chunks/config-switch.mjs +0 -334
- package/dist/chunks/config.mjs +0 -558
- package/dist/chunks/config2.mjs +0 -484
- package/dist/chunks/config3.mjs +0 -486
- package/dist/chunks/constants.mjs +0 -323
- package/dist/chunks/context-opt.mjs +0 -444
- package/dist/chunks/context.mjs +0 -974
- package/dist/chunks/dashboard.mjs +0 -481
- package/dist/chunks/doctor.mjs +0 -1301
- package/dist/chunks/eval.mjs +0 -502
- package/dist/chunks/evolution.mjs +0 -322
- package/dist/chunks/features.mjs +0 -715
- package/dist/chunks/fish.mjs +0 -181
- package/dist/chunks/fs-operations.mjs +0 -180
- package/dist/chunks/health-alerts.mjs +0 -830
- package/dist/chunks/help.mjs +0 -341
- package/dist/chunks/hook-installer.mjs +0 -48
- package/dist/chunks/impact.mjs +0 -651
- package/dist/chunks/index.mjs +0 -23
- package/dist/chunks/index10.mjs +0 -19
- package/dist/chunks/index11.mjs +0 -1171
- package/dist/chunks/index12.mjs +0 -218
- package/dist/chunks/index13.mjs +0 -679
- package/dist/chunks/index14.mjs +0 -1009
- package/dist/chunks/index15.mjs +0 -194
- package/dist/chunks/index2.mjs +0 -7637
- package/dist/chunks/index3.mjs +0 -171
- package/dist/chunks/index4.mjs +0 -26
- package/dist/chunks/index5.mjs +0 -19
- package/dist/chunks/index6.mjs +0 -19092
- package/dist/chunks/index7.mjs +0 -616
- package/dist/chunks/index8.mjs +0 -1602
- package/dist/chunks/index9.mjs +0 -5384
- package/dist/chunks/init.mjs +0 -1911
- package/dist/chunks/installer.mjs +0 -757
- package/dist/chunks/installer2.mjs +0 -103
- package/dist/chunks/interview.mjs +0 -2927
- package/dist/chunks/json-config.mjs +0 -60
- package/dist/chunks/linux.mjs +0 -3863
- package/dist/chunks/macos.mjs +0 -69
- package/dist/chunks/main.mjs +0 -635
- package/dist/chunks/manager.mjs +0 -1048
- package/dist/chunks/marketplace.mjs +0 -265
- package/dist/chunks/mcp-cli.mjs +0 -205
- package/dist/chunks/mcp-performance.mjs +0 -187
- package/dist/chunks/mcp.mjs +0 -667
- package/dist/chunks/memory-check.mjs +0 -2973
- package/dist/chunks/memory-paths.mjs +0 -259
- package/dist/chunks/memory-sync.mjs +0 -209
- package/dist/chunks/memory.mjs +0 -354
- package/dist/chunks/metrics-display.mjs +0 -153
- package/dist/chunks/monitor.mjs +0 -1856
- package/dist/chunks/notification.mjs +0 -1864
- package/dist/chunks/onboarding.mjs +0 -386
- package/dist/chunks/package.mjs +0 -3
- package/dist/chunks/paradigm.mjs +0 -74
- package/dist/chunks/permission-manager.mjs +0 -250
- package/dist/chunks/permissions.mjs +0 -265
- package/dist/chunks/persistence-manager.mjs +0 -801
- package/dist/chunks/persistence.mjs +0 -707
- package/dist/chunks/platform.mjs +0 -395
- package/dist/chunks/plugin.mjs +0 -1936
- package/dist/chunks/powershell.mjs +0 -213
- package/dist/chunks/prompts.mjs +0 -244
- package/dist/chunks/providers.mjs +0 -263
- package/dist/chunks/quick-actions.mjs +0 -335
- package/dist/chunks/quick-provider.mjs +0 -755
- package/dist/chunks/quick-setup.mjs +0 -421
- package/dist/chunks/remote.mjs +0 -497
- package/dist/chunks/research.mjs +0 -1904
- package/dist/chunks/rollback.mjs +0 -38
- package/dist/chunks/session-manager.mjs +0 -1371
- package/dist/chunks/session.mjs +0 -878
- package/dist/chunks/sessions.mjs +0 -106
- package/dist/chunks/silent-updater.mjs +0 -396
- package/dist/chunks/simple-config.mjs +0 -122
- package/dist/chunks/skill.mjs +0 -117
- package/dist/chunks/skill2.mjs +0 -9052
- package/dist/chunks/skills-sync.mjs +0 -1343
- package/dist/chunks/skills.mjs +0 -577
- package/dist/chunks/slash-commands.mjs +0 -208
- package/dist/chunks/smart-guide.mjs +0 -247
- package/dist/chunks/snapshot.mjs +0 -58
- package/dist/chunks/startup.mjs +0 -487
- package/dist/chunks/stats.mjs +0 -191
- package/dist/chunks/status.mjs +0 -471
- package/dist/chunks/team.mjs +0 -63
- package/dist/chunks/thinking.mjs +0 -626
- package/dist/chunks/trace.mjs +0 -57
- package/dist/chunks/uninstall.mjs +0 -852
- package/dist/chunks/update.mjs +0 -174
- package/dist/chunks/upgrade-manager.mjs +0 -204
- package/dist/chunks/upgrade.mjs +0 -133
- package/dist/chunks/version-checker.mjs +0 -891
- package/dist/chunks/vim.mjs +0 -903
- package/dist/chunks/windows.mjs +0 -14
- package/dist/chunks/workflows.mjs +0 -633
- package/dist/chunks/wsl.mjs +0 -129
- package/dist/chunks/zero-config.mjs +0 -871
- package/dist/chunks/zsh.mjs +0 -182
- package/dist/cli.d.mts +0 -1
- package/dist/cli.d.ts +0 -1
- package/dist/cli.mjs +0 -2684
- package/dist/i18n/locales/en/agent-teams.json +0 -18
- package/dist/i18n/locales/en/agentBrowser.json +0 -80
- package/dist/i18n/locales/en/agents.json +0 -135
- package/dist/i18n/locales/en/api.json +0 -63
- package/dist/i18n/locales/en/ccjk-agents.json +0 -33
- package/dist/i18n/locales/en/ccjk-all.json +0 -23
- package/dist/i18n/locales/en/ccjk-skills.json +0 -22
- package/dist/i18n/locales/en/ccjk.json +0 -276
- package/dist/i18n/locales/en/ccr.json +0 -65
- package/dist/i18n/locales/en/claude-md.json +0 -73
- package/dist/i18n/locales/en/cli.json +0 -148
- package/dist/i18n/locales/en/cloud-setup.json +0 -31
- package/dist/i18n/locales/en/cloud-sync.json +0 -147
- package/dist/i18n/locales/en/cloud.json +0 -40
- package/dist/i18n/locales/en/cloudPlugins.json +0 -118
- package/dist/i18n/locales/en/codex.json +0 -184
- package/dist/i18n/locales/en/cometix.json +0 -29
- package/dist/i18n/locales/en/common.json +0 -68
- package/dist/i18n/locales/en/config.json +0 -108
- package/dist/i18n/locales/en/configuration.json +0 -236
- package/dist/i18n/locales/en/context.json +0 -85
- package/dist/i18n/locales/en/dashboard.json +0 -78
- package/dist/i18n/locales/en/errors.json +0 -26
- package/dist/i18n/locales/en/evolution.json +0 -54
- package/dist/i18n/locales/en/hooks.json +0 -74
- package/dist/i18n/locales/en/hooksSync.json +0 -133
- package/dist/i18n/locales/en/installation.json +0 -83
- package/dist/i18n/locales/en/interview.json +0 -104
- package/dist/i18n/locales/en/language.json +0 -19
- package/dist/i18n/locales/en/lsp.json +0 -78
- package/dist/i18n/locales/en/marketplace.json +0 -116
- package/dist/i18n/locales/en/mcp.json +0 -180
- package/dist/i18n/locales/en/memory.json +0 -23
- package/dist/i18n/locales/en/menu.json +0 -299
- package/dist/i18n/locales/en/multi-config.json +0 -79
- package/dist/i18n/locales/en/notification.json +0 -307
- package/dist/i18n/locales/en/permissions.json +0 -95
- package/dist/i18n/locales/en/persistence.json +0 -127
- package/dist/i18n/locales/en/plugins.json +0 -146
- package/dist/i18n/locales/en/quick-actions.json +0 -78
- package/dist/i18n/locales/en/registry.json +0 -54
- package/dist/i18n/locales/en/remote.json +0 -93
- package/dist/i18n/locales/en/sandbox.json +0 -44
- package/dist/i18n/locales/en/setup.json +0 -44
- package/dist/i18n/locales/en/shencha.json +0 -14
- package/dist/i18n/locales/en/skills.json +0 -100
- package/dist/i18n/locales/en/skillsSync.json +0 -74
- package/dist/i18n/locales/en/smartGuide.json +0 -49
- package/dist/i18n/locales/en/stats.json +0 -20
- package/dist/i18n/locales/en/subagent.json +0 -69
- package/dist/i18n/locales/en/superpowers.json +0 -117
- package/dist/i18n/locales/en/team.json +0 -7
- package/dist/i18n/locales/en/thinking.json +0 -65
- package/dist/i18n/locales/en/tools.json +0 -42
- package/dist/i18n/locales/en/uninstall.json +0 -56
- package/dist/i18n/locales/en/updater.json +0 -29
- package/dist/i18n/locales/en/vim.json +0 -169
- package/dist/i18n/locales/en/workflow.json +0 -55
- package/dist/i18n/locales/en/workspace.json +0 -108
- package/dist/i18n/locales/zh-CN/agent-teams.json +0 -18
- package/dist/i18n/locales/zh-CN/agentBrowser.json +0 -80
- package/dist/i18n/locales/zh-CN/agents.json +0 -135
- package/dist/i18n/locales/zh-CN/api.json +0 -63
- package/dist/i18n/locales/zh-CN/ccjk-agents.json +0 -33
- package/dist/i18n/locales/zh-CN/ccjk-all.json +0 -23
- package/dist/i18n/locales/zh-CN/ccjk-skills.json +0 -22
- package/dist/i18n/locales/zh-CN/ccjk.json +0 -276
- package/dist/i18n/locales/zh-CN/ccr.json +0 -65
- package/dist/i18n/locales/zh-CN/claude-md.json +0 -73
- package/dist/i18n/locales/zh-CN/cli.json +0 -148
- package/dist/i18n/locales/zh-CN/cloud-setup.json +0 -31
- package/dist/i18n/locales/zh-CN/cloud-sync.json +0 -147
- package/dist/i18n/locales/zh-CN/cloud.json +0 -40
- package/dist/i18n/locales/zh-CN/cloudPlugins.json +0 -118
- package/dist/i18n/locales/zh-CN/codex.json +0 -184
- package/dist/i18n/locales/zh-CN/cometix.json +0 -29
- package/dist/i18n/locales/zh-CN/common.json +0 -68
- package/dist/i18n/locales/zh-CN/config.json +0 -108
- package/dist/i18n/locales/zh-CN/configuration.json +0 -234
- package/dist/i18n/locales/zh-CN/context.json +0 -85
- package/dist/i18n/locales/zh-CN/dashboard.json +0 -78
- package/dist/i18n/locales/zh-CN/errors.json +0 -26
- package/dist/i18n/locales/zh-CN/evolution.json +0 -54
- package/dist/i18n/locales/zh-CN/hooks.json +0 -74
- package/dist/i18n/locales/zh-CN/hooksSync.json +0 -133
- package/dist/i18n/locales/zh-CN/installation.json +0 -83
- package/dist/i18n/locales/zh-CN/interview.json +0 -104
- package/dist/i18n/locales/zh-CN/language.json +0 -19
- package/dist/i18n/locales/zh-CN/lsp.json +0 -78
- package/dist/i18n/locales/zh-CN/marketplace.json +0 -116
- package/dist/i18n/locales/zh-CN/mcp.json +0 -180
- package/dist/i18n/locales/zh-CN/memory.json +0 -23
- package/dist/i18n/locales/zh-CN/menu.json +0 -299
- package/dist/i18n/locales/zh-CN/multi-config.json +0 -79
- package/dist/i18n/locales/zh-CN/notification.json +0 -307
- package/dist/i18n/locales/zh-CN/permissions.json +0 -95
- package/dist/i18n/locales/zh-CN/persistence.json +0 -127
- package/dist/i18n/locales/zh-CN/plugins.json +0 -146
- package/dist/i18n/locales/zh-CN/quick-actions.json +0 -78
- package/dist/i18n/locales/zh-CN/registry.json +0 -54
- package/dist/i18n/locales/zh-CN/remote.json +0 -93
- package/dist/i18n/locales/zh-CN/sandbox.json +0 -44
- package/dist/i18n/locales/zh-CN/setup.json +0 -44
- package/dist/i18n/locales/zh-CN/shencha.json +0 -14
- package/dist/i18n/locales/zh-CN/skills.json +0 -100
- package/dist/i18n/locales/zh-CN/skillsSync.json +0 -74
- package/dist/i18n/locales/zh-CN/smartGuide.json +0 -49
- package/dist/i18n/locales/zh-CN/stats.json +0 -20
- package/dist/i18n/locales/zh-CN/subagent.json +0 -69
- package/dist/i18n/locales/zh-CN/superpowers.json +0 -117
- package/dist/i18n/locales/zh-CN/team.json +0 -7
- package/dist/i18n/locales/zh-CN/thinking.json +0 -65
- package/dist/i18n/locales/zh-CN/tools.json +0 -42
- package/dist/i18n/locales/zh-CN/uninstall.json +0 -56
- package/dist/i18n/locales/zh-CN/updater.json +0 -29
- package/dist/i18n/locales/zh-CN/vim.json +0 -169
- package/dist/i18n/locales/zh-CN/workflow.json +0 -55
- package/dist/i18n/locales/zh-CN/workspace.json +0 -108
- package/dist/index.d.mts +0 -5658
- package/dist/index.d.ts +0 -5658
- package/dist/index.mjs +0 -3732
- package/dist/shared/ccjk.5bEolFrk.mjs +0 -254
- package/dist/shared/ccjk.8oaxX4iR.mjs +0 -90
- package/dist/shared/ccjk.B2U7DsPy.mjs +0 -31
- package/dist/shared/ccjk.B2f-cwUP.mjs +0 -468
- package/dist/shared/ccjk.BAGoDD49.mjs +0 -36
- package/dist/shared/ccjk.BBtCGd_g.mjs +0 -899
- package/dist/shared/ccjk.BFQ7yr5S.mjs +0 -16
- package/dist/shared/ccjk.BLsIiTqO.mjs +0 -449
- package/dist/shared/ccjk.BXv8aYs1.mjs +0 -170
- package/dist/shared/ccjk.BnsY5WxD.mjs +0 -171
- package/dist/shared/ccjk.BoApaI4j.mjs +0 -28
- package/dist/shared/ccjk.Bq8TqZG_.mjs +0 -189
- package/dist/shared/ccjk.BtrioX1Z.mjs +0 -25
- package/dist/shared/ccjk.Bx_rmYfN.mjs +0 -69
- package/dist/shared/ccjk.BzPbSEP2.mjs +0 -115
- package/dist/shared/ccjk.C0WLUnFV.mjs +0 -293
- package/dist/shared/ccjk.C1hANZTu.mjs +0 -19
- package/dist/shared/ccjk.C2jHOZVP.mjs +0 -52
- package/dist/shared/ccjk.CNhnT6uQ.mjs +0 -636
- package/dist/shared/ccjk.COweQ1RR.mjs +0 -5
- package/dist/shared/ccjk.CfKKcvWy.mjs +0 -126
- package/dist/shared/ccjk.Cjgrln_h.mjs +0 -297
- package/dist/shared/ccjk.CoCHVXl3.mjs +0 -3951
- package/dist/shared/ccjk.CwGZSTAK.mjs +0 -319
- package/dist/shared/ccjk.CxpGa6MC.mjs +0 -2724
- package/dist/shared/ccjk.D-magaEx.mjs +0 -763
- package/dist/shared/ccjk.D0g2ABGg.mjs +0 -171
- package/dist/shared/ccjk.D6ycHbak.mjs +0 -270
- package/dist/shared/ccjk.D75wivnp.mjs +0 -142
- package/dist/shared/ccjk.DDL-4C-k.mjs +0 -100
- package/dist/shared/ccjk.DFRPtmK_.mjs +0 -75
- package/dist/shared/ccjk.DMV3x5Sd.mjs +0 -299
- package/dist/shared/ccjk.DZ2LLOa-.mjs +0 -2195
- package/dist/shared/ccjk.DbigonEQ.mjs +0 -698
- package/dist/shared/ccjk.DcMvE7lf.mjs +0 -618
- package/dist/shared/ccjk.DeWpAShp.mjs +0 -1828
- package/dist/shared/ccjk.DhJ1kyDR.mjs +0 -30
- package/dist/shared/ccjk.DlTXS9rP.mjs +0 -224
- package/dist/shared/ccjk.DopKzo3z.mjs +0 -305
- package/dist/shared/ccjk.DsZsc4LR.mjs +0 -1280
- package/dist/shared/ccjk.DuzJZlgj.mjs +0 -418
- package/dist/shared/ccjk.Dxgd2vjc.mjs +0 -444
- package/dist/shared/ccjk.J8YiPsOw.mjs +0 -259
- package/dist/shared/ccjk.KfSWcGlE.mjs +0 -38
- package/dist/shared/ccjk.L7yC58_i.mjs +0 -225
- package/dist/shared/ccjk.MwtjAULc.mjs +0 -1447
- package/dist/shared/ccjk.OJKHVSOb.mjs +0 -2005
- package/dist/shared/ccjk.OTnevPNE.mjs +0 -225
- package/dist/shared/ccjk.RyizuzOI.mjs +0 -21
- package/dist/shared/ccjk.T_cX87dY.mjs +0 -15
- package/dist/shared/ccjk.bQ7Dh1g4.mjs +0 -249
- package/dist/shared/ccjk.gDEDGD_t.mjs +0 -38
- package/dist/shared/ccjk.hoqrwWdN.mjs +0 -333
- package/dist/shared/ccjk.i_vn-9C3.mjs +0 -317
- package/dist/shared/ccjk.lG3ccFjm.mjs +0 -885
- package/dist/shared/ccjk.wLJHO0Af.mjs +0 -244
- package/dist/shared/ccjk.y-a_1vK4.mjs +0 -5127
- package/dist/templates/agents/README.md +0 -78
- package/dist/templates/agents/fullstack-developer.json +0 -70
- package/dist/templates/agents/go-expert.json +0 -69
- package/dist/templates/agents/index.json +0 -64
- package/dist/templates/agents/python-expert.json +0 -69
- package/dist/templates/agents/react-specialist.json +0 -69
- package/dist/templates/agents/testing-automation-expert.json +0 -70
- package/dist/templates/agents/typescript-architect.json +0 -69
- package/dist/templates/claude-code/common/settings.json +0 -109
- package/dist/templates/common/error-prevention.md +0 -267
- package/dist/templates/common/karpathy-baseline.md +0 -83
- package/dist/templates/common/output-styles/zh-CN/carmack-mode.md +0 -381
- package/dist/templates/common/output-styles/zh-CN/codex-rigor-mode.md +0 -114
- package/dist/templates/common/output-styles/zh-CN/dhh-mode.md +0 -265
- package/dist/templates/common/output-styles/zh-CN/evan-you-mode.md +0 -539
- package/dist/templates/common/output-styles/zh-CN/jobs-mode.md +0 -369
- package/dist/templates/common/output-styles/zh-CN/linus-mode.md +0 -135
- package/dist/templates/common/output-styles/zh-CN/uncle-bob-mode.md +0 -221
- package/dist/templates/common/workflow/continuousDelivery/en/continuous-delivery.md +0 -628
- package/dist/templates/common/workflow/continuousDelivery/zh-CN/continuous-delivery.md +0 -628
- package/dist/templates/common/workflow/essential/en/agents/ccjk-config-agent.md +0 -187
- package/dist/templates/common/workflow/essential/en/agents/ccjk-mcp-agent.md +0 -191
- package/dist/templates/common/workflow/essential/en/agents/ccjk-skill-agent.md +0 -249
- package/dist/templates/common/workflow/essential/en/agents/ccjk-workflow-agent.md +0 -277
- package/dist/templates/common/workflow/essential/en/agents/get-current-datetime.md +0 -29
- package/dist/templates/common/workflow/essential/en/agents/init-architect.md +0 -115
- package/dist/templates/common/workflow/essential/en/agents/ui-ux-designer.md +0 -91
- package/dist/templates/common/workflow/essential/en/feat.md +0 -92
- package/dist/templates/common/workflow/essential/en/goal.md +0 -147
- package/dist/templates/common/workflow/essential/en/init-project.md +0 -53
- package/dist/templates/common/workflow/essential/zh-CN/agents/get-current-datetime.md +0 -29
- package/dist/templates/common/workflow/essential/zh-CN/agents/init-architect.md +0 -115
- package/dist/templates/common/workflow/essential/zh-CN/agents/ui-ux-designer.md +0 -91
- package/dist/templates/common/workflow/essential/zh-CN/feat.md +0 -315
- package/dist/templates/common/workflow/essential/zh-CN/goal.md +0 -146
- package/dist/templates/common/workflow/essential/zh-CN/init-project.md +0 -53
- package/dist/templates/common/workflow/git/en/git-cleanBranches.md +0 -102
- package/dist/templates/common/workflow/git/en/git-commit.md +0 -205
- package/dist/templates/common/workflow/git/en/git-rollback.md +0 -90
- package/dist/templates/common/workflow/git/en/git-worktree.md +0 -276
- package/dist/templates/common/workflow/git/zh-CN/git-cleanBranches.md +0 -102
- package/dist/templates/common/workflow/git/zh-CN/git-commit.md +0 -205
- package/dist/templates/common/workflow/git/zh-CN/git-rollback.md +0 -90
- package/dist/templates/common/workflow/git/zh-CN/git-worktree.md +0 -276
- package/dist/templates/common/workflow/interview/en/interview.md +0 -67
- package/dist/templates/common/workflow/interview/zh-CN/interview.md +0 -67
- package/dist/templates/common/workflow/linearMethod/en/linear-method.md +0 -651
- package/dist/templates/common/workflow/linearMethod/zh-CN/linear-method.md +0 -752
- package/dist/templates/common/workflow/refactoringMaster/en/refactoring-master.md +0 -516
- package/dist/templates/common/workflow/refactoringMaster/zh-CN/refactoring-master.md +0 -812
- package/dist/templates/common/workflow/sixStep/en/workflow.md +0 -83
- package/dist/templates/common/workflow/sixStep/zh-CN/workflow.md +0 -359
- package/dist/templates/common/workflow/specFirstTDD/en/spec-first-tdd.md +0 -364
- package/dist/templates/common/workflow/specFirstTDD/zh-CN/spec-first-tdd.md +0 -366
- package/dist/templates/hooks/README.md +0 -212
- package/dist/templates/hooks/git-workflow-hooks.md +0 -551
- package/dist/templates/hooks/post-test/coverage.json +0 -21
- package/dist/templates/hooks/post-test/summary.json +0 -21
- package/dist/templates/hooks/post-test-coverage.md +0 -434
- package/dist/templates/hooks/pre-commit/eslint.json +0 -22
- package/dist/templates/hooks/pre-commit/prettier.json +0 -22
- package/dist/templates/hooks/pre-commit-black.md +0 -274
- package/dist/templates/hooks/pre-commit-eslint.md +0 -153
- package/dist/templates/hooks/pre-commit-gofmt.md +0 -284
- package/dist/templates/hooks/pre-commit-prettier.md +0 -212
- package/dist/templates/hooks/pre-commit-type-check.md +0 -377
- package/dist/templates/skills/ccjk-init.md +0 -154
- package/dist/templates/skills/ccjk-mcp-setup.md +0 -205
- package/dist/templates/skills/ccjk-troubleshoot.md +0 -228
- package/dist/templates/skills/django-patterns.md +0 -1016
- package/dist/templates/skills/git-workflow.md +0 -748
- package/dist/templates/skills/go-idioms.md +0 -963
- package/dist/templates/skills/index.json +0 -132
- package/dist/templates/skills/nextjs-optimization.md +0 -694
- package/dist/templates/skills/python-pep8.md +0 -852
- package/dist/templates/skills/react-patterns.md +0 -686
- package/dist/templates/skills/rust-patterns.md +0 -1057
- package/dist/templates/skills/security-best-practices.md +0 -1413
- package/dist/templates/skills/testing-best-practices.md +0 -1315
- package/dist/templates/skills/ts-best-practices.md +0 -354
- package/templates/agents/README.md +0 -78
- package/templates/agents/fullstack-developer.json +0 -70
- package/templates/agents/go-expert.json +0 -69
- package/templates/agents/index.json +0 -64
- package/templates/agents/python-expert.json +0 -69
- package/templates/agents/react-specialist.json +0 -69
- package/templates/agents/testing-automation-expert.json +0 -70
- package/templates/agents/typescript-architect.json +0 -69
- package/templates/claude-code/common/settings.json +0 -109
- package/templates/common/error-prevention.md +0 -267
- package/templates/common/karpathy-baseline.md +0 -83
- package/templates/common/output-styles/zh-CN/carmack-mode.md +0 -381
- package/templates/common/output-styles/zh-CN/codex-rigor-mode.md +0 -114
- package/templates/common/output-styles/zh-CN/dhh-mode.md +0 -265
- package/templates/common/output-styles/zh-CN/evan-you-mode.md +0 -539
- package/templates/common/output-styles/zh-CN/jobs-mode.md +0 -369
- package/templates/common/output-styles/zh-CN/linus-mode.md +0 -135
- package/templates/common/output-styles/zh-CN/uncle-bob-mode.md +0 -221
- package/templates/common/workflow/continuousDelivery/en/continuous-delivery.md +0 -628
- package/templates/common/workflow/continuousDelivery/zh-CN/continuous-delivery.md +0 -628
- package/templates/common/workflow/essential/en/agents/ccjk-config-agent.md +0 -187
- package/templates/common/workflow/essential/en/agents/ccjk-mcp-agent.md +0 -191
- package/templates/common/workflow/essential/en/agents/ccjk-skill-agent.md +0 -249
- package/templates/common/workflow/essential/en/agents/ccjk-workflow-agent.md +0 -277
- package/templates/common/workflow/essential/en/agents/get-current-datetime.md +0 -29
- package/templates/common/workflow/essential/en/agents/init-architect.md +0 -115
- package/templates/common/workflow/essential/en/agents/ui-ux-designer.md +0 -91
- package/templates/common/workflow/essential/en/feat.md +0 -92
- package/templates/common/workflow/essential/en/goal.md +0 -147
- package/templates/common/workflow/essential/en/init-project.md +0 -53
- package/templates/common/workflow/essential/zh-CN/agents/get-current-datetime.md +0 -29
- package/templates/common/workflow/essential/zh-CN/agents/init-architect.md +0 -115
- package/templates/common/workflow/essential/zh-CN/agents/ui-ux-designer.md +0 -91
- package/templates/common/workflow/essential/zh-CN/feat.md +0 -315
- package/templates/common/workflow/essential/zh-CN/goal.md +0 -146
- package/templates/common/workflow/essential/zh-CN/init-project.md +0 -53
- package/templates/common/workflow/git/en/git-cleanBranches.md +0 -102
- package/templates/common/workflow/git/en/git-commit.md +0 -205
- package/templates/common/workflow/git/en/git-rollback.md +0 -90
- package/templates/common/workflow/git/en/git-worktree.md +0 -276
- package/templates/common/workflow/git/zh-CN/git-cleanBranches.md +0 -102
- package/templates/common/workflow/git/zh-CN/git-commit.md +0 -205
- package/templates/common/workflow/git/zh-CN/git-rollback.md +0 -90
- package/templates/common/workflow/git/zh-CN/git-worktree.md +0 -276
- package/templates/common/workflow/interview/en/interview.md +0 -67
- package/templates/common/workflow/interview/zh-CN/interview.md +0 -67
- package/templates/common/workflow/linearMethod/en/linear-method.md +0 -651
- package/templates/common/workflow/linearMethod/zh-CN/linear-method.md +0 -752
- package/templates/common/workflow/refactoringMaster/en/refactoring-master.md +0 -516
- package/templates/common/workflow/refactoringMaster/zh-CN/refactoring-master.md +0 -812
- package/templates/common/workflow/sixStep/en/workflow.md +0 -83
- package/templates/common/workflow/sixStep/zh-CN/workflow.md +0 -359
- package/templates/common/workflow/specFirstTDD/en/spec-first-tdd.md +0 -364
- package/templates/common/workflow/specFirstTDD/zh-CN/spec-first-tdd.md +0 -366
- package/templates/hooks/README.md +0 -212
- package/templates/hooks/git-workflow-hooks.md +0 -551
- package/templates/hooks/post-test/coverage.json +0 -21
- package/templates/hooks/post-test/summary.json +0 -21
- package/templates/hooks/post-test-coverage.md +0 -434
- package/templates/hooks/pre-commit/eslint.json +0 -22
- package/templates/hooks/pre-commit/prettier.json +0 -22
- package/templates/hooks/pre-commit-black.md +0 -274
- package/templates/hooks/pre-commit-eslint.md +0 -153
- package/templates/hooks/pre-commit-gofmt.md +0 -284
- package/templates/hooks/pre-commit-prettier.md +0 -212
- package/templates/hooks/pre-commit-type-check.md +0 -377
- package/templates/skills/basic.hbs +0 -72
- package/templates/skills/ccjk-init.md +0 -154
- package/templates/skills/ccjk-mcp-setup.md +0 -205
- package/templates/skills/ccjk-troubleshoot.md +0 -228
- package/templates/skills/code-refactor.hbs +0 -133
- package/templates/skills/code-review.hbs +0 -141
- package/templates/skills/django-patterns.md +0 -1016
- package/templates/skills/git-workflow.md +0 -748
- package/templates/skills/go-idioms.md +0 -963
- package/templates/skills/index.json +0 -132
- package/templates/skills/nextjs-optimization.md +0 -694
- package/templates/skills/python-pep8.md +0 -852
- package/templates/skills/react-patterns.md +0 -686
- package/templates/skills/rust-patterns.md +0 -1057
- package/templates/skills/security-best-practices.md +0 -1413
- package/templates/skills/testing-best-practices.md +0 -1315
- package/templates/skills/ts-best-practices.md +0 -354
- package/templates/skills/type-fix.hbs +0 -132
|
@@ -1,1016 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: django-patterns
|
|
3
|
-
description: Django best practices, model design, view patterns, and ORM optimization
|
|
4
|
-
description_zh: Django 最佳实践、模型设计、视图模式和 ORM 优化
|
|
5
|
-
version: 1.0.0
|
|
6
|
-
category: backend
|
|
7
|
-
triggers: ['/django-patterns', '/django', '/django-orm', '/django-views']
|
|
8
|
-
use_when:
|
|
9
|
-
- Building Django applications with best practices
|
|
10
|
-
- Designing Django models and database schemas
|
|
11
|
-
- Implementing Django views and URL patterns
|
|
12
|
-
- Optimizing Django ORM queries and performance
|
|
13
|
-
use_when_zh:
|
|
14
|
-
- 使用最佳实践构建 Django 应用程序
|
|
15
|
-
- 设计 Django 模型和数据库架构
|
|
16
|
-
- 实现 Django 视图和 URL 模式
|
|
17
|
-
- 优化 Django ORM 查询和性能
|
|
18
|
-
auto_activate: true
|
|
19
|
-
priority: 8
|
|
20
|
-
agents: [django-expert, backend-architect]
|
|
21
|
-
tags: [django, orm, models, views, performance]
|
|
22
|
-
---
|
|
23
|
-
|
|
24
|
-
# Django Patterns | Django 模式
|
|
25
|
-
|
|
26
|
-
## Context | 上下文
|
|
27
|
-
|
|
28
|
-
Use this skill when developing Django applications that require scalable architecture, optimized database queries, and maintainable code structure. Essential for production-ready Django development.
|
|
29
|
-
|
|
30
|
-
在开发需要可扩展架构、优化数据库查询和可维护代码结构的 Django 应用程序时使用此技能。对于生产就绪的 Django 开发至关重要。
|
|
31
|
-
|
|
32
|
-
## Model Design Patterns | 模型设计模式
|
|
33
|
-
|
|
34
|
-
### 1. Model Best Practices | 模型最佳实践
|
|
35
|
-
|
|
36
|
-
```python
|
|
37
|
-
from django.db import models
|
|
38
|
-
from django.contrib.auth.models import AbstractUser
|
|
39
|
-
from django.core.validators import MinValueValidator, MaxValueValidator
|
|
40
|
-
from django.utils import timezone
|
|
41
|
-
from django.urls import reverse
|
|
42
|
-
from typing import Optional
|
|
43
|
-
import uuid
|
|
44
|
-
|
|
45
|
-
# ✅ Good: Well-designed models with proper relationships
|
|
46
|
-
|
|
47
|
-
class TimeStampedModel(models.Model):
|
|
48
|
-
"""Abstract base model with timestamp fields."""
|
|
49
|
-
created_at = models.DateTimeField(auto_now_add=True)
|
|
50
|
-
updated_at = models.DateTimeField(auto_now=True)
|
|
51
|
-
|
|
52
|
-
class Meta:
|
|
53
|
-
abstract = True
|
|
54
|
-
|
|
55
|
-
class User(AbstractUser):
|
|
56
|
-
"""Custom user model extending AbstractUser."""
|
|
57
|
-
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
|
|
58
|
-
email = models.EmailField(unique=True)
|
|
59
|
-
first_name = models.CharField(max_length=150)
|
|
60
|
-
last_name = models.CharField(max_length=150)
|
|
61
|
-
date_of_birth = models.DateField(null=True, blank=True)
|
|
62
|
-
is_verified = models.BooleanField(default=False)
|
|
63
|
-
|
|
64
|
-
USERNAME_FIELD = 'email'
|
|
65
|
-
REQUIRED_FIELDS = ['username', 'first_name', 'last_name']
|
|
66
|
-
|
|
67
|
-
class Meta:
|
|
68
|
-
db_table = 'users'
|
|
69
|
-
indexes = [
|
|
70
|
-
models.Index(fields=['email']),
|
|
71
|
-
models.Index(fields=['is_active', 'is_verified']),
|
|
72
|
-
]
|
|
73
|
-
|
|
74
|
-
def __str__(self) -> str:
|
|
75
|
-
return f"{self.first_name} {self.last_name} ({self.email})"
|
|
76
|
-
|
|
77
|
-
def get_full_name(self) -> str:
|
|
78
|
-
"""Return the full name of the user."""
|
|
79
|
-
return f"{self.first_name} {self.last_name}".strip()
|
|
80
|
-
|
|
81
|
-
class Category(TimeStampedModel):
|
|
82
|
-
"""Product category model."""
|
|
83
|
-
name = models.CharField(max_length=100, unique=True)
|
|
84
|
-
slug = models.SlugField(max_length=100, unique=True)
|
|
85
|
-
description = models.TextField(blank=True)
|
|
86
|
-
parent = models.ForeignKey(
|
|
87
|
-
'self',
|
|
88
|
-
on_delete=models.CASCADE,
|
|
89
|
-
null=True,
|
|
90
|
-
blank=True,
|
|
91
|
-
related_name='children'
|
|
92
|
-
)
|
|
93
|
-
is_active = models.BooleanField(default=True)
|
|
94
|
-
|
|
95
|
-
class Meta:
|
|
96
|
-
verbose_name_plural = "categories"
|
|
97
|
-
ordering = ['name']
|
|
98
|
-
indexes = [
|
|
99
|
-
models.Index(fields=['slug']),
|
|
100
|
-
models.Index(fields=['is_active']),
|
|
101
|
-
]
|
|
102
|
-
|
|
103
|
-
def __str__(self) -> str:
|
|
104
|
-
return self.name
|
|
105
|
-
|
|
106
|
-
def get_absolute_url(self) -> str:
|
|
107
|
-
return reverse('category-detail', kwargs={'slug': self.slug})
|
|
108
|
-
|
|
109
|
-
class Product(TimeStampedModel):
|
|
110
|
-
"""Product model with proper relationships and validation."""
|
|
111
|
-
name = models.CharField(max_length=200)
|
|
112
|
-
slug = models.SlugField(max_length=200, unique=True)
|
|
113
|
-
description = models.TextField()
|
|
114
|
-
price = models.DecimalField(
|
|
115
|
-
max_digits=10,
|
|
116
|
-
decimal_places=2,
|
|
117
|
-
validators=[MinValueValidator(0)]
|
|
118
|
-
)
|
|
119
|
-
stock_quantity = models.PositiveIntegerField(default=0)
|
|
120
|
-
category = models.ForeignKey(
|
|
121
|
-
Category,
|
|
122
|
-
on_delete=models.PROTECT,
|
|
123
|
-
related_name='products'
|
|
124
|
-
)
|
|
125
|
-
tags = models.ManyToManyField('Tag', blank=True, related_name='products')
|
|
126
|
-
is_active = models.BooleanField(default=True)
|
|
127
|
-
featured = models.BooleanField(default=False)
|
|
128
|
-
|
|
129
|
-
class Meta:
|
|
130
|
-
ordering = ['-created_at']
|
|
131
|
-
indexes = [
|
|
132
|
-
models.Index(fields=['slug']),
|
|
133
|
-
models.Index(fields=['category', 'is_active']),
|
|
134
|
-
models.Index(fields=['featured', 'is_active']),
|
|
135
|
-
models.Index(fields=['-created_at']),
|
|
136
|
-
]
|
|
137
|
-
|
|
138
|
-
def __str__(self) -> str:
|
|
139
|
-
return self.name
|
|
140
|
-
|
|
141
|
-
def get_absolute_url(self) -> str:
|
|
142
|
-
return reverse('product-detail', kwargs={'slug': self.slug})
|
|
143
|
-
|
|
144
|
-
@property
|
|
145
|
-
def is_in_stock(self) -> bool:
|
|
146
|
-
"""Check if product is in stock."""
|
|
147
|
-
return self.stock_quantity > 0
|
|
148
|
-
|
|
149
|
-
def reduce_stock(self, quantity: int) -> None:
|
|
150
|
-
"""Reduce stock quantity."""
|
|
151
|
-
if quantity > self.stock_quantity:
|
|
152
|
-
raise ValueError("Insufficient stock")
|
|
153
|
-
self.stock_quantity -= quantity
|
|
154
|
-
self.save(update_fields=['stock_quantity'])
|
|
155
|
-
|
|
156
|
-
class Order(TimeStampedModel):
|
|
157
|
-
"""Order model with proper status management."""
|
|
158
|
-
|
|
159
|
-
class Status(models.TextChoices):
|
|
160
|
-
PENDING = 'pending', 'Pending'
|
|
161
|
-
CONFIRMED = 'confirmed', 'Confirmed'
|
|
162
|
-
SHIPPED = 'shipped', 'Shipped'
|
|
163
|
-
DELIVERED = 'delivered', 'Delivered'
|
|
164
|
-
CANCELLED = 'cancelled', 'Cancelled'
|
|
165
|
-
|
|
166
|
-
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
|
|
167
|
-
user = models.ForeignKey(User, on_delete=models.CASCADE, related_name='orders')
|
|
168
|
-
status = models.CharField(max_length=20, choices=Status.choices, default=Status.PENDING)
|
|
169
|
-
total_amount = models.DecimalField(max_digits=10, decimal_places=2)
|
|
170
|
-
shipping_address = models.TextField()
|
|
171
|
-
notes = models.TextField(blank=True)
|
|
172
|
-
|
|
173
|
-
class Meta:
|
|
174
|
-
ordering = ['-created_at']
|
|
175
|
-
indexes = [
|
|
176
|
-
models.Index(fields=['user', 'status']),
|
|
177
|
-
models.Index(fields=['status', '-created_at']),
|
|
178
|
-
]
|
|
179
|
-
|
|
180
|
-
def __str__(self) -> str:
|
|
181
|
-
return f"Order {self.id} - {self.user.email}"
|
|
182
|
-
|
|
183
|
-
class OrderItem(models.Model):
|
|
184
|
-
"""Order item model for many-to-many relationship with additional fields."""
|
|
185
|
-
order = models.ForeignKey(Order, on_delete=models.CASCADE, related_name='items')
|
|
186
|
-
product = models.ForeignKey(Product, on_delete=models.CASCADE)
|
|
187
|
-
quantity = models.PositiveIntegerField(validators=[MinValueValidator(1)])
|
|
188
|
-
unit_price = models.DecimalField(max_digits=10, decimal_places=2)
|
|
189
|
-
|
|
190
|
-
class Meta:
|
|
191
|
-
unique_together = ['order', 'product']
|
|
192
|
-
|
|
193
|
-
def __str__(self) -> str:
|
|
194
|
-
return f"{self.quantity}x {self.product.name}"
|
|
195
|
-
|
|
196
|
-
@property
|
|
197
|
-
def total_price(self) -> float:
|
|
198
|
-
"""Calculate total price for this item."""
|
|
199
|
-
return float(self.quantity * self.unit_price)
|
|
200
|
-
|
|
201
|
-
# ❌ Bad: Poor model design
|
|
202
|
-
class BadProduct(models.Model):
|
|
203
|
-
name = models.CharField(max_length=1000) # Too long
|
|
204
|
-
price = models.FloatField() # Use DecimalField for money
|
|
205
|
-
category_name = models.CharField(max_length=100) # Should be ForeignKey
|
|
206
|
-
# Missing indexes, validation, and proper relationships
|
|
207
|
-
```
|
|
208
|
-
|
|
209
|
-
### 2. Custom Managers and QuerySets | 自定义管理器和查询集
|
|
210
|
-
|
|
211
|
-
```python
|
|
212
|
-
from django.db import models
|
|
213
|
-
from django.db.models import Q, Count, Avg
|
|
214
|
-
from typing import Optional
|
|
215
|
-
|
|
216
|
-
# ✅ Good: Custom QuerySet and Manager
|
|
217
|
-
|
|
218
|
-
class ProductQuerySet(models.QuerySet):
|
|
219
|
-
"""Custom QuerySet for Product model."""
|
|
220
|
-
|
|
221
|
-
def active(self):
|
|
222
|
-
"""Filter active products."""
|
|
223
|
-
return self.filter(is_active=True)
|
|
224
|
-
|
|
225
|
-
def in_stock(self):
|
|
226
|
-
"""Filter products that are in stock."""
|
|
227
|
-
return self.filter(stock_quantity__gt=0)
|
|
228
|
-
|
|
229
|
-
def by_category(self, category_slug: str):
|
|
230
|
-
"""Filter products by category slug."""
|
|
231
|
-
return self.filter(category__slug=category_slug)
|
|
232
|
-
|
|
233
|
-
def featured(self):
|
|
234
|
-
"""Filter featured products."""
|
|
235
|
-
return self.filter(featured=True)
|
|
236
|
-
|
|
237
|
-
def search(self, query: str):
|
|
238
|
-
"""Search products by name or description."""
|
|
239
|
-
return self.filter(
|
|
240
|
-
Q(name__icontains=query) | Q(description__icontains=query)
|
|
241
|
-
)
|
|
242
|
-
|
|
243
|
-
def with_category(self):
|
|
244
|
-
"""Select related category to avoid N+1 queries."""
|
|
245
|
-
return self.select_related('category')
|
|
246
|
-
|
|
247
|
-
def with_tags(self):
|
|
248
|
-
"""Prefetch tags to avoid N+1 queries."""
|
|
249
|
-
return self.prefetch_related('tags')
|
|
250
|
-
|
|
251
|
-
def popular(self, limit: int = 10):
|
|
252
|
-
"""Get popular products based on order count."""
|
|
253
|
-
return self.annotate(
|
|
254
|
-
order_count=Count('orderitem')
|
|
255
|
-
).order_by('-order_count')[:limit]
|
|
256
|
-
|
|
257
|
-
class ProductManager(models.Manager):
|
|
258
|
-
"""Custom manager for Product model."""
|
|
259
|
-
|
|
260
|
-
def get_queryset(self):
|
|
261
|
-
"""Return custom QuerySet."""
|
|
262
|
-
return ProductQuerySet(self.model, using=self._db)
|
|
263
|
-
|
|
264
|
-
def active(self):
|
|
265
|
-
"""Get active products."""
|
|
266
|
-
return self.get_queryset().active()
|
|
267
|
-
|
|
268
|
-
def available(self):
|
|
269
|
-
"""Get active products that are in stock."""
|
|
270
|
-
return self.get_queryset().active().in_stock()
|
|
271
|
-
|
|
272
|
-
def featured(self):
|
|
273
|
-
"""Get featured products."""
|
|
274
|
-
return self.get_queryset().active().featured()
|
|
275
|
-
|
|
276
|
-
# Add to Product model
|
|
277
|
-
class Product(TimeStampedModel):
|
|
278
|
-
# ... other fields ...
|
|
279
|
-
|
|
280
|
-
objects = ProductManager() # Custom manager
|
|
281
|
-
|
|
282
|
-
# ... rest of the model ...
|
|
283
|
-
|
|
284
|
-
# Usage examples
|
|
285
|
-
# Get all active products with categories
|
|
286
|
-
products = Product.objects.active().with_category()
|
|
287
|
-
|
|
288
|
-
# Search for products
|
|
289
|
-
search_results = Product.objects.active().search("laptop")
|
|
290
|
-
|
|
291
|
-
# Get featured products by category
|
|
292
|
-
featured_laptops = Product.objects.featured().by_category("laptops")
|
|
293
|
-
|
|
294
|
-
# Get popular products
|
|
295
|
-
popular_products = Product.objects.popular(limit=5)
|
|
296
|
-
```
|
|
297
|
-
|
|
298
|
-
## View Patterns | 视图模式
|
|
299
|
-
|
|
300
|
-
### 1. Class-Based Views (CBVs) | 基于类的视图
|
|
301
|
-
|
|
302
|
-
```python
|
|
303
|
-
from django.views.generic import ListView, DetailView, CreateView, UpdateView
|
|
304
|
-
from django.contrib.auth.mixins import LoginRequiredMixin, UserPassesTestMixin
|
|
305
|
-
from django.db.models import Q, Prefetch
|
|
306
|
-
from django.http import JsonResponse
|
|
307
|
-
from django.shortcuts import get_object_or_404
|
|
308
|
-
from django.urls import reverse_lazy
|
|
309
|
-
from django.contrib import messages
|
|
310
|
-
from typing import Any, Dict
|
|
311
|
-
|
|
312
|
-
# ✅ Good: Well-structured CBVs with mixins
|
|
313
|
-
|
|
314
|
-
class ProductListView(ListView):
|
|
315
|
-
"""List view for products with filtering and pagination."""
|
|
316
|
-
model = Product
|
|
317
|
-
template_name = 'products/list.html'
|
|
318
|
-
context_object_name = 'products'
|
|
319
|
-
paginate_by = 20
|
|
320
|
-
|
|
321
|
-
def get_queryset(self):
|
|
322
|
-
"""Get filtered and optimized queryset."""
|
|
323
|
-
queryset = Product.objects.active().with_category().with_tags()
|
|
324
|
-
|
|
325
|
-
# Apply filters
|
|
326
|
-
category_slug = self.request.GET.get('category')
|
|
327
|
-
if category_slug:
|
|
328
|
-
queryset = queryset.by_category(category_slug)
|
|
329
|
-
|
|
330
|
-
search_query = self.request.GET.get('q')
|
|
331
|
-
if search_query:
|
|
332
|
-
queryset = queryset.search(search_query)
|
|
333
|
-
|
|
334
|
-
# Apply ordering
|
|
335
|
-
ordering = self.request.GET.get('ordering', '-created_at')
|
|
336
|
-
if ordering in ['name', '-name', 'price', '-price', '-created_at']:
|
|
337
|
-
queryset = queryset.order_by(ordering)
|
|
338
|
-
|
|
339
|
-
return queryset
|
|
340
|
-
|
|
341
|
-
def get_context_data(self, **kwargs) -> Dict[str, Any]:
|
|
342
|
-
"""Add additional context data."""
|
|
343
|
-
context = super().get_context_data(**kwargs)
|
|
344
|
-
context['categories'] = Category.objects.filter(is_active=True)
|
|
345
|
-
context['current_category'] = self.request.GET.get('category', '')
|
|
346
|
-
context['search_query'] = self.request.GET.get('q', '')
|
|
347
|
-
context['current_ordering'] = self.request.GET.get('ordering', '-created_at')
|
|
348
|
-
return context
|
|
349
|
-
|
|
350
|
-
class ProductDetailView(DetailView):
|
|
351
|
-
"""Detail view for a single product."""
|
|
352
|
-
model = Product
|
|
353
|
-
template_name = 'products/detail.html'
|
|
354
|
-
context_object_name = 'product'
|
|
355
|
-
slug_field = 'slug'
|
|
356
|
-
slug_url_kwarg = 'slug'
|
|
357
|
-
|
|
358
|
-
def get_queryset(self):
|
|
359
|
-
"""Optimize queryset with related objects."""
|
|
360
|
-
return Product.objects.active().with_category().with_tags().select_related('category')
|
|
361
|
-
|
|
362
|
-
def get_context_data(self, **kwargs) -> Dict[str, Any]:
|
|
363
|
-
"""Add related products to context."""
|
|
364
|
-
context = super().get_context_data(**kwargs)
|
|
365
|
-
product = self.object
|
|
366
|
-
|
|
367
|
-
# Get related products from same category
|
|
368
|
-
context['related_products'] = Product.objects.active().filter(
|
|
369
|
-
category=product.category
|
|
370
|
-
).exclude(id=product.id)[:4]
|
|
371
|
-
|
|
372
|
-
return context
|
|
373
|
-
|
|
374
|
-
class ProductCreateView(LoginRequiredMixin, UserPassesTestMixin, CreateView):
|
|
375
|
-
"""Create view for products (admin only)."""
|
|
376
|
-
model = Product
|
|
377
|
-
template_name = 'products/create.html'
|
|
378
|
-
fields = ['name', 'slug', 'description', 'price', 'stock_quantity', 'category', 'tags']
|
|
379
|
-
success_url = reverse_lazy('product-list')
|
|
380
|
-
|
|
381
|
-
def test_func(self) -> bool:
|
|
382
|
-
"""Check if user is staff."""
|
|
383
|
-
return self.request.user.is_staff
|
|
384
|
-
|
|
385
|
-
def form_valid(self, form):
|
|
386
|
-
"""Add success message."""
|
|
387
|
-
messages.success(self.request, 'Product created successfully!')
|
|
388
|
-
return super().form_valid(form)
|
|
389
|
-
|
|
390
|
-
class OrderListView(LoginRequiredMixin, ListView):
|
|
391
|
-
"""List view for user's orders."""
|
|
392
|
-
model = Order
|
|
393
|
-
template_name = 'orders/list.html'
|
|
394
|
-
context_object_name = 'orders'
|
|
395
|
-
paginate_by = 10
|
|
396
|
-
|
|
397
|
-
def get_queryset(self):
|
|
398
|
-
"""Get orders for current user."""
|
|
399
|
-
return Order.objects.filter(
|
|
400
|
-
user=self.request.user
|
|
401
|
-
).prefetch_related(
|
|
402
|
-
Prefetch(
|
|
403
|
-
'items',
|
|
404
|
-
queryset=OrderItem.objects.select_related('product')
|
|
405
|
-
)
|
|
406
|
-
).order_by('-created_at')
|
|
407
|
-
|
|
408
|
-
# ✅ Good: API views with proper error handling
|
|
409
|
-
from django.views import View
|
|
410
|
-
from django.http import JsonResponse
|
|
411
|
-
from django.views.decorators.csrf import csrf_exempt
|
|
412
|
-
from django.utils.decorators import method_decorator
|
|
413
|
-
import json
|
|
414
|
-
|
|
415
|
-
@method_decorator(csrf_exempt, name='dispatch')
|
|
416
|
-
class ProductAPIView(View):
|
|
417
|
-
"""API view for products."""
|
|
418
|
-
|
|
419
|
-
def get(self, request, *args, **kwargs):
|
|
420
|
-
"""Get products as JSON."""
|
|
421
|
-
try:
|
|
422
|
-
products = Product.objects.active().with_category()
|
|
423
|
-
|
|
424
|
-
# Apply filters
|
|
425
|
-
category = request.GET.get('category')
|
|
426
|
-
if category:
|
|
427
|
-
products = products.by_category(category)
|
|
428
|
-
|
|
429
|
-
data = [
|
|
430
|
-
{
|
|
431
|
-
'id': str(product.id),
|
|
432
|
-
'name': product.name,
|
|
433
|
-
'slug': product.slug,
|
|
434
|
-
'price': str(product.price),
|
|
435
|
-
'category': product.category.name,
|
|
436
|
-
'in_stock': product.is_in_stock,
|
|
437
|
-
}
|
|
438
|
-
for product in products[:20] # Limit results
|
|
439
|
-
]
|
|
440
|
-
|
|
441
|
-
return JsonResponse({'products': data})
|
|
442
|
-
|
|
443
|
-
except Exception as e:
|
|
444
|
-
return JsonResponse({'error': str(e)}, status=500)
|
|
445
|
-
|
|
446
|
-
def post(self, request, *args, **kwargs):
|
|
447
|
-
"""Create new product via API."""
|
|
448
|
-
if not request.user.is_authenticated or not request.user.is_staff:
|
|
449
|
-
return JsonResponse({'error': 'Unauthorized'}, status=401)
|
|
450
|
-
|
|
451
|
-
try:
|
|
452
|
-
data = json.loads(request.body)
|
|
453
|
-
|
|
454
|
-
# Validate required fields
|
|
455
|
-
required_fields = ['name', 'slug', 'description', 'price', 'category_id']
|
|
456
|
-
for field in required_fields:
|
|
457
|
-
if field not in data:
|
|
458
|
-
return JsonResponse({'error': f'Missing field: {field}'}, status=400)
|
|
459
|
-
|
|
460
|
-
# Create product
|
|
461
|
-
product = Product.objects.create(
|
|
462
|
-
name=data['name'],
|
|
463
|
-
slug=data['slug'],
|
|
464
|
-
description=data['description'],
|
|
465
|
-
price=data['price'],
|
|
466
|
-
category_id=data['category_id'],
|
|
467
|
-
stock_quantity=data.get('stock_quantity', 0),
|
|
468
|
-
)
|
|
469
|
-
|
|
470
|
-
return JsonResponse({
|
|
471
|
-
'id': str(product.id),
|
|
472
|
-
'name': product.name,
|
|
473
|
-
'slug': product.slug,
|
|
474
|
-
}, status=201)
|
|
475
|
-
|
|
476
|
-
except json.JSONDecodeError:
|
|
477
|
-
return JsonResponse({'error': 'Invalid JSON'}, status=400)
|
|
478
|
-
except Exception as e:
|
|
479
|
-
return JsonResponse({'error': str(e)}, status=500)
|
|
480
|
-
```
|
|
481
|
-
|
|
482
|
-
### 2. Function-Based Views (FBVs) | 基于函数的视图
|
|
483
|
-
|
|
484
|
-
```python
|
|
485
|
-
from django.shortcuts import render, redirect, get_object_or_404
|
|
486
|
-
from django.contrib.auth.decorators import login_required
|
|
487
|
-
from django.contrib import messages
|
|
488
|
-
from django.db import transaction
|
|
489
|
-
from django.http import HttpRequest, HttpResponse
|
|
490
|
-
from django.core.paginator import Paginator
|
|
491
|
-
|
|
492
|
-
# ✅ Good: Well-structured FBVs
|
|
493
|
-
|
|
494
|
-
def product_list(request: HttpRequest) -> HttpResponse:
|
|
495
|
-
"""List products with filtering and pagination."""
|
|
496
|
-
# Get base queryset
|
|
497
|
-
products = Product.objects.active().with_category()
|
|
498
|
-
|
|
499
|
-
# Apply filters
|
|
500
|
-
category_slug = request.GET.get('category')
|
|
501
|
-
if category_slug:
|
|
502
|
-
products = products.by_category(category_slug)
|
|
503
|
-
|
|
504
|
-
search_query = request.GET.get('q')
|
|
505
|
-
if search_query:
|
|
506
|
-
products = products.search(search_query)
|
|
507
|
-
|
|
508
|
-
# Pagination
|
|
509
|
-
paginator = Paginator(products, 20)
|
|
510
|
-
page_number = request.GET.get('page')
|
|
511
|
-
page_obj = paginator.get_page(page_number)
|
|
512
|
-
|
|
513
|
-
context = {
|
|
514
|
-
'page_obj': page_obj,
|
|
515
|
-
'categories': Category.objects.filter(is_active=True),
|
|
516
|
-
'current_category': category_slug or '',
|
|
517
|
-
'search_query': search_query or '',
|
|
518
|
-
}
|
|
519
|
-
|
|
520
|
-
return render(request, 'products/list.html', context)
|
|
521
|
-
|
|
522
|
-
@login_required
|
|
523
|
-
@transaction.atomic
|
|
524
|
-
def create_order(request: HttpRequest) -> HttpResponse:
|
|
525
|
-
"""Create a new order with items."""
|
|
526
|
-
if request.method == 'POST':
|
|
527
|
-
try:
|
|
528
|
-
# Get cart items from session
|
|
529
|
-
cart_items = request.session.get('cart', {})
|
|
530
|
-
if not cart_items:
|
|
531
|
-
messages.error(request, 'Your cart is empty.')
|
|
532
|
-
return redirect('cart')
|
|
533
|
-
|
|
534
|
-
# Calculate total
|
|
535
|
-
total_amount = 0
|
|
536
|
-
order_items = []
|
|
537
|
-
|
|
538
|
-
for product_id, quantity in cart_items.items():
|
|
539
|
-
product = get_object_or_404(Product, id=product_id, is_active=True)
|
|
540
|
-
|
|
541
|
-
if product.stock_quantity < quantity:
|
|
542
|
-
messages.error(
|
|
543
|
-
request,
|
|
544
|
-
f'Insufficient stock for {product.name}. Available: {product.stock_quantity}'
|
|
545
|
-
)
|
|
546
|
-
return redirect('cart')
|
|
547
|
-
|
|
548
|
-
item_total = product.price * quantity
|
|
549
|
-
total_amount += item_total
|
|
550
|
-
|
|
551
|
-
order_items.append({
|
|
552
|
-
'product': product,
|
|
553
|
-
'quantity': quantity,
|
|
554
|
-
'unit_price': product.price,
|
|
555
|
-
})
|
|
556
|
-
|
|
557
|
-
# Create order
|
|
558
|
-
order = Order.objects.create(
|
|
559
|
-
user=request.user,
|
|
560
|
-
total_amount=total_amount,
|
|
561
|
-
shipping_address=request.POST.get('shipping_address', ''),
|
|
562
|
-
notes=request.POST.get('notes', ''),
|
|
563
|
-
)
|
|
564
|
-
|
|
565
|
-
# Create order items and update stock
|
|
566
|
-
for item_data in order_items:
|
|
567
|
-
OrderItem.objects.create(
|
|
568
|
-
order=order,
|
|
569
|
-
product=item_data['product'],
|
|
570
|
-
quantity=item_data['quantity'],
|
|
571
|
-
unit_price=item_data['unit_price'],
|
|
572
|
-
)
|
|
573
|
-
|
|
574
|
-
# Reduce stock
|
|
575
|
-
item_data['product'].reduce_stock(item_data['quantity'])
|
|
576
|
-
|
|
577
|
-
# Clear cart
|
|
578
|
-
request.session['cart'] = {}
|
|
579
|
-
|
|
580
|
-
messages.success(request, f'Order {order.id} created successfully!')
|
|
581
|
-
return redirect('order-detail', pk=order.id)
|
|
582
|
-
|
|
583
|
-
except Exception as e:
|
|
584
|
-
messages.error(request, f'Error creating order: {str(e)}')
|
|
585
|
-
return redirect('cart')
|
|
586
|
-
|
|
587
|
-
# GET request - show order form
|
|
588
|
-
cart_items = request.session.get('cart', {})
|
|
589
|
-
if not cart_items:
|
|
590
|
-
messages.info(request, 'Your cart is empty.')
|
|
591
|
-
return redirect('product-list')
|
|
592
|
-
|
|
593
|
-
# Calculate cart total
|
|
594
|
-
cart_products = []
|
|
595
|
-
total = 0
|
|
596
|
-
|
|
597
|
-
for product_id, quantity in cart_items.items():
|
|
598
|
-
try:
|
|
599
|
-
product = Product.objects.get(id=product_id, is_active=True)
|
|
600
|
-
item_total = product.price * quantity
|
|
601
|
-
total += item_total
|
|
602
|
-
|
|
603
|
-
cart_products.append({
|
|
604
|
-
'product': product,
|
|
605
|
-
'quantity': quantity,
|
|
606
|
-
'total': item_total,
|
|
607
|
-
})
|
|
608
|
-
except Product.DoesNotExist:
|
|
609
|
-
continue
|
|
610
|
-
|
|
611
|
-
context = {
|
|
612
|
-
'cart_products': cart_products,
|
|
613
|
-
'total': total,
|
|
614
|
-
}
|
|
615
|
-
|
|
616
|
-
return render(request, 'orders/create.html', context)
|
|
617
|
-
|
|
618
|
-
def add_to_cart(request: HttpRequest, product_id: int) -> HttpResponse:
|
|
619
|
-
"""Add product to cart."""
|
|
620
|
-
product = get_object_or_404(Product, id=product_id, is_active=True)
|
|
621
|
-
|
|
622
|
-
if not product.is_in_stock:
|
|
623
|
-
messages.error(request, f'{product.name} is out of stock.')
|
|
624
|
-
return redirect('product-detail', slug=product.slug)
|
|
625
|
-
|
|
626
|
-
# Get or initialize cart
|
|
627
|
-
cart = request.session.get('cart', {})
|
|
628
|
-
|
|
629
|
-
# Add or update quantity
|
|
630
|
-
if str(product_id) in cart:
|
|
631
|
-
cart[str(product_id)] += 1
|
|
632
|
-
else:
|
|
633
|
-
cart[str(product_id)] = 1
|
|
634
|
-
|
|
635
|
-
# Check stock limit
|
|
636
|
-
if cart[str(product_id)] > product.stock_quantity:
|
|
637
|
-
cart[str(product_id)] = product.stock_quantity
|
|
638
|
-
messages.warning(
|
|
639
|
-
request,
|
|
640
|
-
f'Only {product.stock_quantity} items available for {product.name}.'
|
|
641
|
-
)
|
|
642
|
-
else:
|
|
643
|
-
messages.success(request, f'{product.name} added to cart.')
|
|
644
|
-
|
|
645
|
-
# Save cart to session
|
|
646
|
-
request.session['cart'] = cart
|
|
647
|
-
|
|
648
|
-
return redirect('product-detail', slug=product.slug)
|
|
649
|
-
```
|
|
650
|
-
|
|
651
|
-
## ORM Optimization | ORM 优化
|
|
652
|
-
|
|
653
|
-
### 1. Query Optimization | 查询优化
|
|
654
|
-
|
|
655
|
-
```python
|
|
656
|
-
from django.db.models import Prefetch, Count, Sum, Avg, F, Q
|
|
657
|
-
from django.db import connection
|
|
658
|
-
|
|
659
|
-
# ✅ Good: Optimized queries
|
|
660
|
-
|
|
661
|
-
def get_products_with_categories():
|
|
662
|
-
"""Get products with categories in a single query."""
|
|
663
|
-
return Product.objects.select_related('category').filter(is_active=True)
|
|
664
|
-
|
|
665
|
-
def get_orders_with_items():
|
|
666
|
-
"""Get orders with items and products in optimized queries."""
|
|
667
|
-
return Order.objects.prefetch_related(
|
|
668
|
-
Prefetch(
|
|
669
|
-
'items',
|
|
670
|
-
queryset=OrderItem.objects.select_related('product__category')
|
|
671
|
-
)
|
|
672
|
-
).select_related('user')
|
|
673
|
-
|
|
674
|
-
def get_category_stats():
|
|
675
|
-
"""Get category statistics with aggregation."""
|
|
676
|
-
return Category.objects.annotate(
|
|
677
|
-
product_count=Count('products', filter=Q(products__is_active=True)),
|
|
678
|
-
avg_price=Avg('products__price', filter=Q(products__is_active=True)),
|
|
679
|
-
total_stock=Sum('products__stock_quantity', filter=Q(products__is_active=True))
|
|
680
|
-
).filter(is_active=True)
|
|
681
|
-
|
|
682
|
-
def get_user_order_summary(user_id: int):
|
|
683
|
-
"""Get user order summary with aggregation."""
|
|
684
|
-
return User.objects.filter(id=user_id).aggregate(
|
|
685
|
-
total_orders=Count('orders'),
|
|
686
|
-
total_spent=Sum('orders__total_amount'),
|
|
687
|
-
avg_order_value=Avg('orders__total_amount')
|
|
688
|
-
)
|
|
689
|
-
|
|
690
|
-
def bulk_update_prices(category_id: int, percentage_increase: float):
|
|
691
|
-
"""Bulk update product prices using F expressions."""
|
|
692
|
-
Product.objects.filter(category_id=category_id).update(
|
|
693
|
-
price=F('price') * (1 + percentage_increase / 100)
|
|
694
|
-
)
|
|
695
|
-
|
|
696
|
-
def get_popular_products_by_category():
|
|
697
|
-
"""Get popular products grouped by category."""
|
|
698
|
-
return Product.objects.select_related('category').annotate(
|
|
699
|
-
order_count=Count('orderitem')
|
|
700
|
-
).filter(
|
|
701
|
-
is_active=True,
|
|
702
|
-
order_count__gt=0
|
|
703
|
-
).order_by('category__name', '-order_count')
|
|
704
|
-
|
|
705
|
-
# ✅ Good: Using raw SQL when necessary
|
|
706
|
-
def get_monthly_sales_report():
|
|
707
|
-
"""Get monthly sales report using raw SQL."""
|
|
708
|
-
with connection.cursor() as cursor:
|
|
709
|
-
cursor.execute("""
|
|
710
|
-
SELECT
|
|
711
|
-
DATE_TRUNC('month', created_at) as month,
|
|
712
|
-
COUNT(*) as order_count,
|
|
713
|
-
SUM(total_amount) as total_revenue
|
|
714
|
-
FROM orders
|
|
715
|
-
WHERE created_at >= %s
|
|
716
|
-
GROUP BY DATE_TRUNC('month', created_at)
|
|
717
|
-
ORDER BY month DESC
|
|
718
|
-
""", [timezone.now() - timedelta(days=365)])
|
|
719
|
-
|
|
720
|
-
columns = [col[0] for col in cursor.description]
|
|
721
|
-
return [dict(zip(columns, row)) for row in cursor.fetchall()]
|
|
722
|
-
|
|
723
|
-
# ❌ Bad: N+1 queries
|
|
724
|
-
def bad_get_products_with_categories():
|
|
725
|
-
products = Product.objects.filter(is_active=True)
|
|
726
|
-
for product in products:
|
|
727
|
-
print(product.category.name) # N+1 query!
|
|
728
|
-
|
|
729
|
-
def bad_get_order_totals():
|
|
730
|
-
orders = Order.objects.all()
|
|
731
|
-
for order in orders:
|
|
732
|
-
total = sum(item.total_price for item in order.items.all()) # N+1 query!
|
|
733
|
-
```
|
|
734
|
-
|
|
735
|
-
### 2. Database Indexes and Performance | 数据库索引和性能
|
|
736
|
-
|
|
737
|
-
```python
|
|
738
|
-
# ✅ Good: Proper indexing in models
|
|
739
|
-
|
|
740
|
-
class Product(TimeStampedModel):
|
|
741
|
-
name = models.CharField(max_length=200)
|
|
742
|
-
slug = models.SlugField(max_length=200, unique=True)
|
|
743
|
-
price = models.DecimalField(max_digits=10, decimal_places=2)
|
|
744
|
-
category = models.ForeignKey(Category, on_delete=models.PROTECT)
|
|
745
|
-
is_active = models.BooleanField(default=True)
|
|
746
|
-
featured = models.BooleanField(default=False)
|
|
747
|
-
|
|
748
|
-
class Meta:
|
|
749
|
-
indexes = [
|
|
750
|
-
# Single column indexes
|
|
751
|
-
models.Index(fields=['slug']),
|
|
752
|
-
models.Index(fields=['is_active']),
|
|
753
|
-
models.Index(fields=['featured']),
|
|
754
|
-
|
|
755
|
-
# Composite indexes for common query patterns
|
|
756
|
-
models.Index(fields=['category', 'is_active']),
|
|
757
|
-
models.Index(fields=['is_active', 'featured']),
|
|
758
|
-
models.Index(fields=['category', 'is_active', '-created_at']),
|
|
759
|
-
|
|
760
|
-
# Partial indexes (PostgreSQL)
|
|
761
|
-
models.Index(
|
|
762
|
-
fields=['price'],
|
|
763
|
-
condition=models.Q(is_active=True),
|
|
764
|
-
name='active_products_price_idx'
|
|
765
|
-
),
|
|
766
|
-
]
|
|
767
|
-
|
|
768
|
-
# ✅ Good: Database-specific optimizations
|
|
769
|
-
from django.contrib.postgres.indexes import GinIndex
|
|
770
|
-
from django.contrib.postgres.search import SearchVectorField
|
|
771
|
-
|
|
772
|
-
class Article(TimeStampedModel):
|
|
773
|
-
"""Article model with full-text search (PostgreSQL)."""
|
|
774
|
-
title = models.CharField(max_length=200)
|
|
775
|
-
content = models.TextField()
|
|
776
|
-
search_vector = SearchVectorField(null=True)
|
|
777
|
-
|
|
778
|
-
class Meta:
|
|
779
|
-
indexes = [
|
|
780
|
-
GinIndex(fields=['search_vector']),
|
|
781
|
-
]
|
|
782
|
-
|
|
783
|
-
# Custom migration for search vector
|
|
784
|
-
# migrations/xxxx_add_search_vector.py
|
|
785
|
-
from django.contrib.postgres.search import SearchVector
|
|
786
|
-
from django.db import migrations
|
|
787
|
-
|
|
788
|
-
def update_search_vector(apps, schema_editor):
|
|
789
|
-
Article = apps.get_model('myapp', 'Article')
|
|
790
|
-
Article.objects.update(
|
|
791
|
-
search_vector=SearchVector('title', weight='A') + SearchVector('content', weight='B')
|
|
792
|
-
)
|
|
793
|
-
|
|
794
|
-
class Migration(migrations.Migration):
|
|
795
|
-
dependencies = [
|
|
796
|
-
('myapp', '0001_initial'),
|
|
797
|
-
]
|
|
798
|
-
|
|
799
|
-
operations = [
|
|
800
|
-
migrations.RunPython(update_search_vector),
|
|
801
|
-
]
|
|
802
|
-
```
|
|
803
|
-
|
|
804
|
-
## Testing Django Applications | 测试 Django 应用程序
|
|
805
|
-
|
|
806
|
-
### 1. Model and View Testing | 模型和视图测试
|
|
807
|
-
|
|
808
|
-
```python
|
|
809
|
-
from django.test import TestCase, Client
|
|
810
|
-
from django.contrib.auth import get_user_model
|
|
811
|
-
from django.urls import reverse
|
|
812
|
-
from django.core.exceptions import ValidationError
|
|
813
|
-
from decimal import Decimal
|
|
814
|
-
|
|
815
|
-
User = get_user_model()
|
|
816
|
-
|
|
817
|
-
class ProductModelTest(TestCase):
|
|
818
|
-
"""Test cases for Product model."""
|
|
819
|
-
|
|
820
|
-
def setUp(self):
|
|
821
|
-
"""Set up test data."""
|
|
822
|
-
self.category = Category.objects.create(
|
|
823
|
-
name="Electronics",
|
|
824
|
-
slug="electronics"
|
|
825
|
-
)
|
|
826
|
-
self.user = User.objects.create_user(
|
|
827
|
-
username="testuser",
|
|
828
|
-
email="test@example.com",
|
|
829
|
-
password="testpass123"
|
|
830
|
-
)
|
|
831
|
-
|
|
832
|
-
def test_product_creation(self):
|
|
833
|
-
"""Test product creation with valid data."""
|
|
834
|
-
product = Product.objects.create(
|
|
835
|
-
name="Test Product",
|
|
836
|
-
slug="test-product",
|
|
837
|
-
description="Test description",
|
|
838
|
-
price=Decimal('99.99'),
|
|
839
|
-
stock_quantity=10,
|
|
840
|
-
category=self.category
|
|
841
|
-
)
|
|
842
|
-
|
|
843
|
-
self.assertEqual(product.name, "Test Product")
|
|
844
|
-
self.assertEqual(product.slug, "test-product")
|
|
845
|
-
self.assertTrue(product.is_in_stock)
|
|
846
|
-
self.assertEqual(str(product), "Test Product")
|
|
847
|
-
|
|
848
|
-
def test_product_reduce_stock(self):
|
|
849
|
-
"""Test stock reduction functionality."""
|
|
850
|
-
product = Product.objects.create(
|
|
851
|
-
name="Test Product",
|
|
852
|
-
slug="test-product",
|
|
853
|
-
description="Test description",
|
|
854
|
-
price=Decimal('99.99'),
|
|
855
|
-
stock_quantity=10,
|
|
856
|
-
category=self.category
|
|
857
|
-
)
|
|
858
|
-
|
|
859
|
-
# Test successful stock reduction
|
|
860
|
-
product.reduce_stock(5)
|
|
861
|
-
self.assertEqual(product.stock_quantity, 5)
|
|
862
|
-
|
|
863
|
-
# Test insufficient stock error
|
|
864
|
-
with self.assertRaises(ValueError):
|
|
865
|
-
product.reduce_stock(10)
|
|
866
|
-
|
|
867
|
-
def test_product_absolute_url(self):
|
|
868
|
-
"""Test product absolute URL."""
|
|
869
|
-
product = Product.objects.create(
|
|
870
|
-
name="Test Product",
|
|
871
|
-
slug="test-product",
|
|
872
|
-
description="Test description",
|
|
873
|
-
price=Decimal('99.99'),
|
|
874
|
-
category=self.category
|
|
875
|
-
)
|
|
876
|
-
|
|
877
|
-
expected_url = reverse('product-detail', kwargs={'slug': 'test-product'})
|
|
878
|
-
self.assertEqual(product.get_absolute_url(), expected_url)
|
|
879
|
-
|
|
880
|
-
class ProductViewTest(TestCase):
|
|
881
|
-
"""Test cases for Product views."""
|
|
882
|
-
|
|
883
|
-
def setUp(self):
|
|
884
|
-
"""Set up test data."""
|
|
885
|
-
self.client = Client()
|
|
886
|
-
self.category = Category.objects.create(
|
|
887
|
-
name="Electronics",
|
|
888
|
-
slug="electronics"
|
|
889
|
-
)
|
|
890
|
-
self.product = Product.objects.create(
|
|
891
|
-
name="Test Product",
|
|
892
|
-
slug="test-product",
|
|
893
|
-
description="Test description",
|
|
894
|
-
price=Decimal('99.99'),
|
|
895
|
-
stock_quantity=10,
|
|
896
|
-
category=self.category
|
|
897
|
-
)
|
|
898
|
-
|
|
899
|
-
def test_product_list_view(self):
|
|
900
|
-
"""Test product list view."""
|
|
901
|
-
response = self.client.get(reverse('product-list'))
|
|
902
|
-
|
|
903
|
-
self.assertEqual(response.status_code, 200)
|
|
904
|
-
self.assertContains(response, "Test Product")
|
|
905
|
-
self.assertIn('products', response.context)
|
|
906
|
-
|
|
907
|
-
def test_product_detail_view(self):
|
|
908
|
-
"""Test product detail view."""
|
|
909
|
-
response = self.client.get(
|
|
910
|
-
reverse('product-detail', kwargs={'slug': 'test-product'})
|
|
911
|
-
)
|
|
912
|
-
|
|
913
|
-
self.assertEqual(response.status_code, 200)
|
|
914
|
-
self.assertEqual(response.context['product'], self.product)
|
|
915
|
-
self.assertContains(response, "Test Product")
|
|
916
|
-
|
|
917
|
-
def test_product_list_filtering(self):
|
|
918
|
-
"""Test product list filtering by category."""
|
|
919
|
-
response = self.client.get(
|
|
920
|
-
reverse('product-list'),
|
|
921
|
-
{'category': 'electronics'}
|
|
922
|
-
)
|
|
923
|
-
|
|
924
|
-
self.assertEqual(response.status_code, 200)
|
|
925
|
-
self.assertContains(response, "Test Product")
|
|
926
|
-
|
|
927
|
-
def test_product_search(self):
|
|
928
|
-
"""Test product search functionality."""
|
|
929
|
-
response = self.client.get(
|
|
930
|
-
reverse('product-list'),
|
|
931
|
-
{'q': 'Test'}
|
|
932
|
-
)
|
|
933
|
-
|
|
934
|
-
self.assertEqual(response.status_code, 200)
|
|
935
|
-
self.assertContains(response, "Test Product")
|
|
936
|
-
|
|
937
|
-
class OrderViewTest(TestCase):
|
|
938
|
-
"""Test cases for Order views."""
|
|
939
|
-
|
|
940
|
-
def setUp(self):
|
|
941
|
-
"""Set up test data."""
|
|
942
|
-
self.client = Client()
|
|
943
|
-
self.user = User.objects.create_user(
|
|
944
|
-
username="testuser",
|
|
945
|
-
email="test@example.com",
|
|
946
|
-
password="testpass123"
|
|
947
|
-
)
|
|
948
|
-
self.category = Category.objects.create(
|
|
949
|
-
name="Electronics",
|
|
950
|
-
slug="electronics"
|
|
951
|
-
)
|
|
952
|
-
self.product = Product.objects.create(
|
|
953
|
-
name="Test Product",
|
|
954
|
-
slug="test-product",
|
|
955
|
-
description="Test description",
|
|
956
|
-
price=Decimal('99.99'),
|
|
957
|
-
stock_quantity=10,
|
|
958
|
-
category=self.category
|
|
959
|
-
)
|
|
960
|
-
|
|
961
|
-
def test_create_order_requires_login(self):
|
|
962
|
-
"""Test that creating order requires authentication."""
|
|
963
|
-
response = self.client.post(reverse('create-order'))
|
|
964
|
-
self.assertEqual(response.status_code, 302) # Redirect to login
|
|
965
|
-
|
|
966
|
-
def test_create_order_success(self):
|
|
967
|
-
"""Test successful order creation."""
|
|
968
|
-
self.client.login(username="testuser", password="testpass123")
|
|
969
|
-
|
|
970
|
-
# Add item to cart
|
|
971
|
-
session = self.client.session
|
|
972
|
-
session['cart'] = {str(self.product.id): 2}
|
|
973
|
-
session.save()
|
|
974
|
-
|
|
975
|
-
response = self.client.post(reverse('create-order'), {
|
|
976
|
-
'shipping_address': '123 Test St, Test City',
|
|
977
|
-
'notes': 'Test order'
|
|
978
|
-
})
|
|
979
|
-
|
|
980
|
-
self.assertEqual(response.status_code, 302) # Redirect after success
|
|
981
|
-
|
|
982
|
-
# Check order was created
|
|
983
|
-
order = Order.objects.get(user=self.user)
|
|
984
|
-
self.assertEqual(order.total_amount, Decimal('199.98'))
|
|
985
|
-
self.assertEqual(order.items.count(), 1)
|
|
986
|
-
|
|
987
|
-
# Check stock was reduced
|
|
988
|
-
self.product.refresh_from_db()
|
|
989
|
-
self.assertEqual(self.product.stock_quantity, 8)
|
|
990
|
-
```
|
|
991
|
-
|
|
992
|
-
## Performance Checklist | 性能检查清单
|
|
993
|
-
|
|
994
|
-
- [ ] Database queries are optimized with select_related and prefetch_related
|
|
995
|
-
- [ ] Proper indexes are defined for common query patterns
|
|
996
|
-
- [ ] N+1 query problems are avoided
|
|
997
|
-
- [ ] Bulk operations are used for large data sets
|
|
998
|
-
- [ ] Database-level constraints and validations are implemented
|
|
999
|
-
- [ ] Caching is implemented for expensive queries
|
|
1000
|
-
- [ ] Database connection pooling is configured
|
|
1001
|
-
- [ ] Query performance is monitored and analyzed
|
|
1002
|
-
- [ ] Raw SQL is used judiciously for complex queries
|
|
1003
|
-
- [ ] Database migrations are optimized for production
|
|
1004
|
-
|
|
1005
|
-
## 性能检查清单
|
|
1006
|
-
|
|
1007
|
-
- [ ] 使用 select_related 和 prefetch_related 优化数据库查询
|
|
1008
|
-
- [ ] 为常见查询模式定义适当的索引
|
|
1009
|
-
- [ ] 避免 N+1 查询问题
|
|
1010
|
-
- [ ] 对大数据集使用批量操作
|
|
1011
|
-
- [ ] 实现数据库级约束和验证
|
|
1012
|
-
- [ ] 为昂贵的查询实现缓存
|
|
1013
|
-
- [ ] 配置数据库连接池
|
|
1014
|
-
- [ ] 监控和分析查询性能
|
|
1015
|
-
- [ ] 明智地使用原始 SQL 进行复杂查询
|
|
1016
|
-
- [ ] 为生产环境优化数据库迁移
|