创作经历
++ 我是一名热爱开源的开发者,长期活跃于 GitHub 社区。 + 在这里,我分享我的代码片段、开源项目以及技术学习笔记。 + 我相信代码可以改变世界,也希望通过我的项目能为社区带来一些微小的价值。 +
++ 主要技术栈: Python, Flask, HTML/CSS, JavaScript +
+ADMIN CONSOLE
+欢迎,管理员!
本页面为前端登录演示,未连接真实账号系统。
演示提示:输入任意用户名和 6 位以上密码即可体验完整登录流程。
接入真实后端时,将表单 action 替换为实际登录接口地址,并移除脚本中的演示拦截逻辑即可。
+ 我是一名热爱开源的开发者,长期活跃于 GitHub 社区。 + 在这里,我分享我的代码片段、开源项目以及技术学习笔记。 + 我相信代码可以改变世界,也希望通过我的项目能为社区带来一些微小的价值。 +
++ 主要技术栈: Python, Flask, HTML/CSS, JavaScript +
+请输入您的账号信息
+ + + + + +还没有账号?立即注册
+填写以下信息完成注册
+ + + + + +已有账号?去登录
+$;?Spu-%-jYV?Hio1B+54ep=wuRnxu;xur3e^PRf$63{MPn%Kcjvf+OhSMWj;a$V%kR;`wzhYI98%*96q`p#66pUzq=91HsUX zFpAXMLYF0UJrjD?!=fGTu)-ayVd*7{BE7cIV+lRm293|hk*2YJ_Hlo3Y=lq4xY8&S z(eU584L5~Ska-^gj4F?7WgaE)x=Le-S!8PGorh(J7K4jukx|&waKfu#Tna1(GL;-u zxkRN}Ifj;46N*`{)ha}l&?0Nr`x-tBmEdm!i_8P&mka>6y1qs!>aTDiUe%;gw5R!+ zuR{EPL;k}C5Av_20{+$qwRk{(g$6F_00Y3Sk;b}A)Epqy!yLsEg(cw4N4yIb$^(#c zb7|FmMnR`zH-bId*uf)ZD5Yt5_>H2W5q?{bK-A7tH8T>U1bmhyrxpVWOG9XhU1V$V zbf&`6pj2+Yxk=j$g7xry(uS(#YNp~UUmpr$&JQ}^Sobd(oN!`t!OYEI9nf#$3xsCZ zTOAW%tXWx~0UiauHE5oeB?X&c7_1BdJMh2?G%=EH(09V=tU~F*aH54g6tGan CiHB_eZxctcgvw*lCHKwm4vk1FPcTn$&eC^6Qa1b?b@!_oH{CrS{X$5^rw=QQ`=Q z*eS34qD(@LftW}JoJcjUNGE+j%d}+FE%OQe-f-d-N=-qnVK^+9*sJ~MrxA@w*KDMh zuBN0*`lvVz0xU|;@mSeg9WAe+d4;%Sy4#>lHEl*H4ZRCL<8u&2w7EYRJo6%s>f7x^ zpOxraUa%4)cH*p+I9t59#wUKh^wXtM)0->&(Gq`Z?dZg9q15n(6&`-x(EjmY@!h`( z`)r}z653aVj^zQDk6Wy8%Ld|v$XavfdVBwGCcX;7U?YUY1DibbUc^wm$*$|O>iVA5 z^{>Yo?O2x;>spQV0ENesFGUnNR1!K~#$iyZ>)-Y#wv8UR@#Feq=T5RuLQUregHPJR zv_BX;cT9LP!qfiA;CY5mHaR>EM_N9|%bg+5_Jkta2~KOW@t*eq#+N@vJ=43vp73;2 z^kX)q%USpm15aIY1Dw!e&-<35b+#iKv>M!zJgKJ%MKd`_h3iI|?p(t=A^@hG1XHxM z*;`zOPs40M!)M4itf3jiSOx)gHpBb{#s7+WzeESWL~R>15B{p-gN|b8t1x1sw~yP= zE-Tu#in`Z?*4xdt&}s>-pS-;yB#WWVP>4CT*$`w-ZX7@iZ=?Md+Fxou{AtHZ@~u@g zx)y8pe6akM6+2?bhOO9eQP@ndOvh%df$7{FkeJ>LsMNT&;t88=wb<5Gw*Ajs>oczX N?_A3>uFZwRe*hV7z@`8I literal 0 HcmV?d00001 diff --git a/migrations/alembic.ini b/migrations/alembic.ini new file mode 100644 index 0000000..ec9d45c --- /dev/null +++ b/migrations/alembic.ini @@ -0,0 +1,50 @@ +# A generic, single database configuration. + +[alembic] +# template used to generate migration files +# file_template = %%(rev)s_%%(slug)s + +# set to 'true' to run the environment during +# the 'revision' command, regardless of autogenerate +# revision_environment = false + + +# Logging configuration +[loggers] +keys = root,sqlalchemy,alembic,flask_migrate + +[handlers] +keys = console + +[formatters] +keys = generic + +[logger_root] +level = WARN +handlers = console +qualname = + +[logger_sqlalchemy] +level = WARN +handlers = +qualname = sqlalchemy.engine + +[logger_alembic] +level = INFO +handlers = +qualname = alembic + +[logger_flask_migrate] +level = INFO +handlers = +qualname = flask_migrate + +[handler_console] +class = StreamHandler +args = (sys.stderr,) +level = NOTSET +formatter = generic + +[formatter_generic] +format = %(levelname)-5.5s [%(name)s] %(message)s +datefmt = %H:%M:%S diff --git a/migrations/env.py b/migrations/env.py new file mode 100644 index 0000000..4c97092 --- /dev/null +++ b/migrations/env.py @@ -0,0 +1,113 @@ +import logging +from logging.config import fileConfig + +from flask import current_app + +from alembic import context + +# this is the Alembic Config object, which provides +# access to the values within the .ini file in use. +config = context.config + +# Interpret the config file for Python logging. +# This line sets up loggers basically. +fileConfig(config.config_file_name) +logger = logging.getLogger('alembic.env') + + +def get_engine(): + try: + # this works with Flask-SQLAlchemy<3 and Alchemical + return current_app.extensions['migrate'].db.get_engine() + except (TypeError, AttributeError): + # this works with Flask-SQLAlchemy>=3 + return current_app.extensions['migrate'].db.engine + + +def get_engine_url(): + try: + return get_engine().url.render_as_string(hide_password=False).replace( + '%', '%%') + except AttributeError: + return str(get_engine().url).replace('%', '%%') + + +# add your model's MetaData object here +# for 'autogenerate' support +# from myapp import mymodel +# target_metadata = mymodel.Base.metadata +config.set_main_option('sqlalchemy.url', get_engine_url()) +target_db = current_app.extensions['migrate'].db + +# other values from the config, defined by the needs of env.py, +# can be acquired: +# my_important_option = config.get_main_option("my_important_option") +# ... etc. + + +def get_metadata(): + if hasattr(target_db, 'metadatas'): + return target_db.metadatas[None] + return target_db.metadata + + +def run_migrations_offline(): + """Run migrations in 'offline' mode. + + This configures the context with just a URL + and not an Engine, though an Engine is acceptable + here as well. By skipping the Engine creation + we don't even need a DBAPI to be available. + + Calls to context.execute() here emit the given string to the + script output. + + """ + url = config.get_main_option("sqlalchemy.url") + context.configure( + url=url, target_metadata=get_metadata(), literal_binds=True + ) + + with context.begin_transaction(): + context.run_migrations() + + +def run_migrations_online(): + """Run migrations in 'online' mode. + + In this scenario we need to create an Engine + and associate a connection with the context. + + """ + + # this callback is used to prevent an auto-migration from being generated + # when there are no changes to the schema + # reference: http://alembic.zzzcomputing.com/en/latest/cookbook.html + def process_revision_directives(context, revision, directives): + if getattr(config.cmd_opts, 'autogenerate', False): + script = directives[0] + if script.upgrade_ops.is_empty(): + directives[:] = [] + logger.info('No changes in schema detected.') + + conf_args = current_app.extensions['migrate'].configure_args + if conf_args.get("process_revision_directives") is None: + conf_args["process_revision_directives"] = process_revision_directives + + connectable = get_engine() + + with connectable.connect() as connection: + context.configure( + connection=connection, + target_metadata=get_metadata(), + **conf_args + ) + + with context.begin_transaction(): + context.run_migrations() + + +if context.is_offline_mode(): + run_migrations_offline() +else: + run_migrations_online() diff --git a/migrations/script.py.mako b/migrations/script.py.mako new file mode 100644 index 0000000..2c01563 --- /dev/null +++ b/migrations/script.py.mako @@ -0,0 +1,24 @@ +"""${message} + +Revision ID: ${up_revision} +Revises: ${down_revision | comma,n} +Create Date: ${create_date} + +""" +from alembic import op +import sqlalchemy as sa +${imports if imports else ""} + +# revision identifiers, used by Alembic. +revision = ${repr(up_revision)} +down_revision = ${repr(down_revision)} +branch_labels = ${repr(branch_labels)} +depends_on = ${repr(depends_on)} + + +def upgrade(): + ${upgrades if upgrades else "pass"} + + +def downgrade(): + ${downgrades if downgrades else "pass"} diff --git a/migrations/versions/67c0c267b827_创建表.py b/migrations/versions/67c0c267b827_创建表.py new file mode 100644 index 0000000..df088dd --- /dev/null +++ b/migrations/versions/67c0c267b827_创建表.py @@ -0,0 +1,37 @@ +"""创建表 + +Revision ID: 67c0c267b827 +Revises: +Create Date: 2026-08-26 00:13:19.101390 + +""" +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision = '67c0c267b827' +down_revision = None +branch_labels = None +depends_on = None + + +def upgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.create_table('user', + sa.Column('id', sa.Integer(), nullable=False), + sa.Column('username', sa.String(length=80), nullable=False), + sa.Column('email', sa.String(length=120), nullable=True), + sa.Column('password', sa.String(length=200), nullable=False), + sa.PrimaryKeyConstraint('id'), + sa.UniqueConstraint('email'), + sa.UniqueConstraint('password'), + sa.UniqueConstraint('username') + ) + # ### end Alembic commands ### + + +def downgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.drop_table('user') + # ### end Alembic commands ### diff --git a/migrations/versions/__pycache__/5655a068d090_创建表.cpython-312.pyc b/migrations/versions/__pycache__/5655a068d090_创建表.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..5640cb74baaed326949b8e9c259d1deb599f6ea1 GIT binary patch literal 1822 zcmb^yOK1~8bU(A7tc@S62J0m%6oORKNMa4*!B(Mus%S+Cfn}Q++AaHuvztor(t@BD zp{0c?3gWF&51tjgdhjHTD5EzO^roi0JG04{R0}EMz 9?B=Pxop--=LTT V}|jK#h4O_ zgd!JA@+A2I-JuvNoSjrT)!L!dCf8f%oLX)YP<{%QnOC~nZbTovamq22mYWfAeX&!F zN}I+1ALB+A)i#e^Jo47wA?uASYMZpEZ}=TIvKZT>#eaC2Hp^RES#t_%`~^Gc#Wty@ za=qnP!-ptTg_JQuK*_?8NH#o8%U<^?j$@5Eh=59fFwq(IEEf?4xt8sW61{8%!3`h7 zl#W>~lCDzJTV@ztlLCvVgT7O7J)#YJAsVMvx-VkewQ%(;st)>I5Ms;rLZV&{vF(kM z?N>bedIhy4PlP%PaqR>}r}D+CXsme7pF)E)&Y Vl$mxD#2Le7OHn>VE3M!DmVh {8QLaNV(-(gbp=nDr6Sar1vb;>?%XV#l!Ml_pGD-^dAw z0Z|!wFfe^Dst(M;_%mGnxa-4;gHGW6RLW**kfG?iEQ;a}Nfp7`RzXaB5E9>Yp{r+F af4{wZI^IC>9n;27b(^Txdu6dF%HkLBY+JGb literal 0 HcmV?d00001 diff --git a/migrations/versions/__pycache__/67c0c267b827_创建表.cpython-312.pyc b/migrations/versions/__pycache__/67c0c267b827_创建表.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..7fadbf08055204e93e327f8f8bb424eaca7af76b GIT binary patch literal 1587 zcmb7EUx*V&7@yrtHk-XfJZedeRMQiMK)oh8dUpn~#k&e;^^{ssLSWfshTI -zxmDg&3xZH zOQ#J4 R7Y{uimC`c=EN$P;7XjtDjLiLOr6B9 zp-PfyK#g`RpJ-iz?Qd?}fAi$Gzkgjbj34Nd9oW8?o0~1?Ca3C!dTDa1HeH(PS!hts z88eJpA cS#N z8W>0qb;ls7RTL`Huqcz13RsB}#-+HZNp7$bb;zy{^paceGbHv9uZvS*4DI+BVtmLR zJxFhtq(pm|SntRDKT!2;P?JNb#*Skh*# BL+HB(cF7TahkheF z^ovWs^aEF-xHsyG;%@eIAxD(2Gayy9bA;!#h=H1$=}Pbl{(q+$DelaH`36!!&ck3+iS5-=F3N62OpCLxnrV} z{p#_d4*nJlf8u@5@xHsW8~BSz$~?!JyNL~)8%V|mKKP-uHJXfT>@$e=gPs>xE@pQe z$o4%i>mYi6b)Jbf&8xkKGYIpWy=P+?&_{={y} ?@v>`vj6Q)W0x@q!+ G?;KSwPHmc#Zrv;;nm;lw$=DZQy%}Bot>HX z<2|vJ&24RWwy@OBxjJ^jedeFXb_B8l+t?Cb(J+L!O?c{f3a;rP=cnA=3d=!r<`Q-! z&S+PRfiuS7iS4TC9+>v`eqb#d**yyKp)FnCP&@e2XxSy(vWBN!gFp2qa5DE*l1rL$ zt=*C8yXMrc(H-G5wMTF3;1jjmj=PoGoNEJbZe_#hquF=Hg3}#25;z&00