<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" ><generator uri="https://jekyllrb.com/" version="3.9.2">Jekyll</generator><link href="/feed.xml" rel="self" type="application/atom+xml" /><link href="/" rel="alternate" type="text/html" /><updated>2022-10-25T16:51:40+08:00</updated><id>/feed.xml</id><title type="html">大灰狼.BLOG</title><subtitle>I'm a pythoner. This is my personal tech blog.</subtitle><entry><title type="html">New Blog New Post</title><link href="/2018/06/04/new-blog-new-post.html" rel="alternate" type="text/html" title="New Blog New Post" /><published>2018-06-04T16:54:00+08:00</published><updated>2018-06-04T16:54:00+08:00</updated><id>/2018/06/04/new-blog-new-post</id><content type="html" xml:base="/2018/06/04/new-blog-new-post.html">&lt;p&gt;看看是不是直接就可以拿 Github Web Editor 作为博客的编辑器了。&lt;/p&gt;</content><author><name></name></author><summary type="html">看看是不是直接就可以拿 Github Web Editor 作为博客的编辑器了。</summary></entry><entry><title type="html">当 SqlAlchemy 没有了外键</title><link href="/2016/12/27/sqlalchemy-without-foreignkey.html" rel="alternate" type="text/html" title="当 SqlAlchemy 没有了外键" /><published>2016-12-27T04:00:00+08:00</published><updated>2016-12-27T04:00:00+08:00</updated><id>/2016/12/27/sqlalchemy-without-foreignkey</id><content type="html" xml:base="/2016/12/27/sqlalchemy-without-foreignkey.html">&lt;p&gt;目前团队习惯于不在数据库中使用外键（ForeignKey），大概是出于性能以及对脏数据的容忍度的考虑吧。这本来没什么，但是由于我们使用 SqlAlchemy 作为 ORM，就带来了一个问题：如何在不使用外键的情况下使用 SqlAlchemy 中的各种高级特性呢（如：relationship，单表继承，join表继承等）？&lt;/p&gt;

&lt;p&gt;首先需要明确的是，上面提到的高级特性其实对外键并没有什么直接的依赖关系的，只不过如果存在外键定义的话，很多特性参数可以根据外键的定义自动配置好。&lt;/p&gt;

&lt;p&gt;仔细查阅了 SqlAlchemy 的文档，终于整理好了各种场景下的使用方法。&lt;/p&gt;

&lt;p&gt;后续的示例代码假设都已经执行了如下片段:&lt;/p&gt;

&lt;div class=&quot;language-python highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;sqlalchemy&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;as&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;sa&lt;/span&gt;
&lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;sqlalchemy.orm&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;as&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;orm&lt;/span&gt;
&lt;span class=&quot;kn&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;sqlalchemy.ext.declarative&lt;/span&gt; &lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;declarative_base&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;Base&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;declarative_base&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;one-to-many-relationship&quot;&gt;One-to-Many Relationship&lt;/h2&gt;
&lt;p&gt;首先看标准的带外键的表达如下:&lt;/p&gt;

&lt;div class=&quot;language-python highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Parent&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Base&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;__tablename__&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;'parent'&lt;/span&gt;
    &lt;span class=&quot;nb&quot;&gt;id&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;sa&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Column&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;sa&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Integer&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;primary_key&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;bp&quot;&gt;True&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;children&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;orm&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;relationship&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;'Child'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;backref&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;'parent'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Child&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Base&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;__tablename__&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;'child'&lt;/span&gt;
    &lt;span class=&quot;nb&quot;&gt;id&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;sa&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Column&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;sa&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Integer&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;primary_key&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;bp&quot;&gt;True&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;parent_id&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;sa&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Column&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;sa&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Integer&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;sa&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ForeignKey&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;'parent.id'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;我们只需要标注出关联的目标，具体如何关联等信息 ORM 会根据外键帮我们自动配置好。&lt;/p&gt;

&lt;p&gt;而如果不用外键的话，所有的关联关系都需要明确指定，代码就会复杂一些了：&lt;/p&gt;

&lt;div class=&quot;language-python highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Parent&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Base&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;__tablename__&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;'parent'&lt;/span&gt;
    &lt;span class=&quot;nb&quot;&gt;id&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;sa&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Column&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;sa&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Integer&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;primary_key&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;bp&quot;&gt;True&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;children&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;orm&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;relationship&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
        &lt;span class=&quot;s&quot;&gt;'Child'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;primaryjoin&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;'Parent.id == Child.parent_id'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;foreign_keys&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;'Child.parent_id'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;backref&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;'parent'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Child&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Base&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;__tablename__&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;'child'&lt;/span&gt;
    &lt;span class=&quot;nb&quot;&gt;id&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;sa&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Column&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;sa&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Integer&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;primary_key&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;bp&quot;&gt;True&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;parent_id&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;sa&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Column&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;sa&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Integer&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;sa&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ForeignKey&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;'parent.id'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;如上，需要明确指定 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;primary join&lt;/code&gt; 和 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;foriegn_keys&lt;/code&gt; 这两个参数。&lt;/p&gt;

&lt;h2 id=&quot;one-to-one-relationship&quot;&gt;One-to-One Relationship&lt;/h2&gt;
&lt;p&gt;各个只是在一对多关联的基础上添加一个 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;uselist=False&lt;/code&gt; 的参数，没啥好说的。&lt;/p&gt;

&lt;div class=&quot;language-python highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Parent&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Base&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;__tablename__&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;'parent'&lt;/span&gt;
    &lt;span class=&quot;nb&quot;&gt;id&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;sa&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Column&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;sa&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Integer&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;primary_key&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;bp&quot;&gt;True&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;child&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;orm&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;relationship&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
        &lt;span class=&quot;s&quot;&gt;'Child'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;primaryjoin&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;'Parent.id == Child.parent_id'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;foreign_keys&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;'Child.parent_id'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;uselist&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;bp&quot;&gt;False&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;backref&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;'parent'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Child&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Base&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;__tablename__&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;'child'&lt;/span&gt;
    &lt;span class=&quot;nb&quot;&gt;id&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;sa&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Column&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;sa&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Integer&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;primary_key&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;bp&quot;&gt;True&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;parent_id&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;sa&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Column&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;sa&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Integer&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;sa&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ForeignKey&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;'parent.id'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;many-to-many-relationship&quot;&gt;Many-to-Many Relationship&lt;/h2&gt;
&lt;p&gt;同样，先看使用 ForeignKey 的版本：&lt;/p&gt;

&lt;div class=&quot;language-python highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;association_table&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;sa&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Table&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;'association'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Base&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;metadata&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;sa&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Column&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;'left_id'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;sa&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Integer&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;sa&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ForeignKey&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;'left.id'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)),&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;sa&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Column&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;'right_id'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;sa&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Integer&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;sa&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ForeignKey&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;'right.id'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Parent&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Base&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;__tablename__&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;'left'&lt;/span&gt;
    &lt;span class=&quot;nb&quot;&gt;id&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;sa&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Column&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;sa&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Integer&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;primary_key&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;bp&quot;&gt;True&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;children&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;orm&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;relationship&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
        &lt;span class=&quot;s&quot;&gt;&quot;Child&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;secondary&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;association_table&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;backref&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;'parents'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Child&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Base&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;__tablename__&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;'right'&lt;/span&gt;
    &lt;span class=&quot;nb&quot;&gt;id&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Column&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Integer&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;primary_key&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;bp&quot;&gt;True&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;不用 ForeignKey 的版本:&lt;/p&gt;

&lt;div class=&quot;language-python highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;association_table&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;sa&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Table&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;'association'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Base&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;metadata&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;sa&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Column&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;'left_id'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;sa&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Integer&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;sa&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ForeignKey&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;'left.id'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)),&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;sa&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Column&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;'right_id'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;sa&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Integer&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;sa&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ForeignKey&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;'right.id'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Parent&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Base&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;__tablename__&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;'left'&lt;/span&gt;
    &lt;span class=&quot;nb&quot;&gt;id&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;sa&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Column&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;sa&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Integer&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;primary_key&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;bp&quot;&gt;True&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;children&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;orm&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;relationship&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
        &lt;span class=&quot;s&quot;&gt;&quot;Child&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;secondary&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;association_table&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;primaryjoin&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;'left.id == association.left_id'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;secondaryjoin&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;'right.id == association.right_id'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;backref&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;'parents'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Child&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Base&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;__tablename__&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;'right'&lt;/span&gt;
    &lt;span class=&quot;nb&quot;&gt;id&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Column&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Integer&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;primary_key&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;bp&quot;&gt;True&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;single-table-inheritance&quot;&gt;Single Table Inheritance&lt;/h2&gt;
&lt;p&gt;单表继承不需要用到外键，所以可以直接使用：&lt;/p&gt;

&lt;div class=&quot;language-python highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Person&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Base&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;__tablename__&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;'people'&lt;/span&gt;
    &lt;span class=&quot;nb&quot;&gt;id&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;sa&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Column&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;sa&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Integer&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;primary_key&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;bp&quot;&gt;True&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;discriminator&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;sa&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Column&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;'type'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;sa&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;50&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;__mapper_args__&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;'polymorphic_on'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;discriminator&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Engineer&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Person&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;__mapper_args__&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;'polymorphic_identity'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;'engineer'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;primary_language&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;sa&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Column&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;sa&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;50&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;joined-table-inheritance&quot;&gt;Joined Table Inheritance&lt;/h2&gt;
&lt;p&gt;外键版本如下：&lt;/p&gt;

&lt;div class=&quot;language-python highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Person&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Base&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;__tablename__&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;'people'&lt;/span&gt;
    &lt;span class=&quot;nb&quot;&gt;id&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;sa&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Column&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;sa&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Integer&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;primary_key&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;bp&quot;&gt;True&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;discriminator&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;sa&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Column&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;'type'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;sa&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;50&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;__mapper_args__&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;'polymorphic_on'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;discriminator&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Engineer&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Person&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;__tablename__&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;'engineers'&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;__mapper_args__&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;'polymorphic_identity'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;'engineer'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;nb&quot;&gt;id&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;sa&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Column&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Integer&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;sa&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ForeignKey&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;'people.id'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;primary_key&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;bp&quot;&gt;True&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;primary_language&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;sa&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Column&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;sa&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;50&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;去外键版本如下：&lt;/p&gt;

&lt;div class=&quot;language-python highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Person&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Base&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;__tablename__&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;'people'&lt;/span&gt;
    &lt;span class=&quot;nb&quot;&gt;id&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;sa&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Column&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;sa&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Integer&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;primary_key&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;bp&quot;&gt;True&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;discriminator&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;sa&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Column&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;'type'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;sa&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;50&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;__mapper_args__&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;'polymorphic_on'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;discriminator&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Engineer&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Person&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;__tablename__&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;'engineers'&lt;/span&gt;

    &lt;span class=&quot;n&quot;&gt;_id&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;sa&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Column&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Integer&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;primary_key&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;bp&quot;&gt;True&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;__mapper_args__&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;s&quot;&gt;'polymorphic_identity'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;'engineer'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;s&quot;&gt;'inherit_condition'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;_id&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Person&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;id&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt;
        &lt;span class=&quot;s&quot;&gt;'inherit_foreign_keys'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;_id&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;nb&quot;&gt;id&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;orm&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;column_property&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;_id&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Person&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;id&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;del&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;_id&lt;/span&gt;

    &lt;span class=&quot;n&quot;&gt;primary_language&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;sa&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Column&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;sa&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;50&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;这里有两个重点，分别是如何表达继承关联，以及如何将两个表的 id 列映射到一个字段上。&lt;/p&gt;

&lt;p&gt;在 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;__mapper_args__&lt;/code&gt; 中我们手动指定了 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;inherit_condition&lt;/code&gt; 和 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;inherit_foreign_keys&lt;/code&gt; 这两个参数来表达和父表的关系。&lt;/p&gt;

&lt;p&gt;用 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;orm.column_property&lt;/code&gt; 可以将多个 column 映射到同一个字段上，这里将子表的 id 和父表的 id 都映射到了 id 这个字段上了，并在结束时，清理掉了多余的 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;_id&lt;/code&gt; 这个引用。&lt;/p&gt;

&lt;p&gt;其实这里为什么需要将两个 id 列映射到用一个字段上我也不是很清楚，这么做只是为了完整的还原使用外键版本的结果。&lt;/p&gt;

&lt;h2 id=&quot;小结&quot;&gt;小结&lt;/h2&gt;

&lt;p&gt;所以说，为啥不用外键呢？&lt;/p&gt;</content><author><name></name></author><category term="Database" /><category term="ORM" /><category term="SQLAlachemy" /><summary type="html">目前团队习惯于不在数据库中使用外键（ForeignKey），大概是出于性能以及对脏数据的容忍度的考虑吧。这本来没什么，但是由于我们使用 SqlAlchemy 作为 ORM，就带来了一个问题：如何在不使用外键的情况下使用 SqlAlchemy 中的各种高级特性呢（如：relationship，单表继承，join表继承等）？</summary></entry><entry><title type="html">SSL 客户端验证</title><link href="/2016/09/27/ssl-client-side-authentication.html" rel="alternate" type="text/html" title="SSL 客户端验证" /><published>2016-09-27T04:00:00+08:00</published><updated>2016-09-27T04:00:00+08:00</updated><id>/2016/09/27/ssl-client-side-authentication</id><content type="html" xml:base="/2016/09/27/ssl-client-side-authentication.html">&lt;p&gt;对于自己的个人网站，安全要求没有那么重要。如果想对网站做一个简单的身份验证，可以有两种方式：&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;直接在 Nginx 中配置一个简单的 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;auth_basic&lt;/code&gt; 形式的用户名 &amp;amp; 密码；&lt;/li&gt;
  &lt;li&gt;或者使用本文要说的 &lt;strong&gt;SSL Client Side Authentication&lt;/strong&gt;；&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;注：由于 &lt;strong&gt;Client Side Authentication&lt;/strong&gt; 太长，下文会用 &lt;strong&gt;CSA&lt;/strong&gt; 这个缩写来代替。&lt;/p&gt;

&lt;h2 id=&quot;为什么用这个&quot;&gt;为什么用这个&lt;/h2&gt;

&lt;p&gt;两个原因：&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;安全。这个不用说了，只有拥有证书的人才能被认证，就排除了密码被破解的可能性；&lt;/li&gt;
  &lt;li&gt;方便。不用输入用户名密码什么的了啊；&lt;/li&gt;
&lt;/ol&gt;

&lt;h2 id=&quot;配置过程&quot;&gt;配置过程&lt;/h2&gt;

&lt;p&gt;首先需要明确，CSA 的配置过程和网站的 &lt;strong&gt;Server Side Authentication&lt;/strong&gt; 是完全独立的，两者没有什么关系。&lt;/p&gt;

&lt;h3 id=&quot;环境准备&quot;&gt;环境准备&lt;/h3&gt;

&lt;p&gt;安装 openssl 工具。&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;brew &lt;span class=&quot;nb&quot;&gt;install &lt;/span&gt;openssl
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;准备一个空目录。&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nb&quot;&gt;mkdir &lt;/span&gt;certs
&lt;span class=&quot;nb&quot;&gt;cd &lt;/span&gt;certs
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;创建根证书&quot;&gt;创建根证书&lt;/h3&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c&quot;&gt;# generate primary key&lt;/span&gt;
openssl genrsa &lt;span class=&quot;nt&quot;&gt;-des3&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-out&lt;/span&gt; ca.key 4096
&lt;span class=&quot;c&quot;&gt;# or if you don't want a password:&lt;/span&gt;
openssl genrsa &lt;span class=&quot;nt&quot;&gt;-out&lt;/span&gt; ca.key 4096
&lt;span class=&quot;c&quot;&gt;# generate a cert&lt;/span&gt;
openssl req &lt;span class=&quot;nt&quot;&gt;-new&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-x509&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-days&lt;/span&gt; 365 &lt;span class=&quot;nt&quot;&gt;-key&lt;/span&gt; ca.key &lt;span class=&quot;nt&quot;&gt;-out&lt;/span&gt; ca.crt
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;生成 ca.crt 文件的时候会需要填写一些信息，看着填就好了，关系不大。我是这么填写的：&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;openssl req &lt;span class=&quot;nt&quot;&gt;-new&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-x509&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-days&lt;/span&gt; 365 &lt;span class=&quot;nt&quot;&gt;-key&lt;/span&gt; ca.key &lt;span class=&quot;nt&quot;&gt;-out&lt;/span&gt; ca.crt
You are about to be asked to enter information that will be incorporated
into your certificate request.
What you are about to enter is what is called a Distinguished Name or a DN.
There are quite a few fields but you can leave some blank
For some fields there will be a default value,
If you enter &lt;span class=&quot;s1&quot;&gt;'.'&lt;/span&gt;, the field will be left blank.
&lt;span class=&quot;nt&quot;&gt;------&lt;/span&gt;
Country Name &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;2 letter code&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;AU]:CN
State or Province Name &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;full name&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;Some-State]:Beijing
Locality Name &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;eg, city&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;[]&lt;/span&gt;:Beijing
Organization Name &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;eg, company&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;Internet Widgits Pty Ltd]:
Organizational Unit Name &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;eg, section&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;[]&lt;/span&gt;:Hongbo He
Common Name &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;e.g. server FQDN or YOUR name&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;[]&lt;/span&gt;:Hongbo He
Email Address &lt;span class=&quot;o&quot;&gt;[]&lt;/span&gt;:me@graycarl.me
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;然后生成的证书长这样：&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/fs/17-08-03-600e4f7a.png&quot; alt=&quot;cert-1&quot; /&gt;&lt;/p&gt;

&lt;p&gt;这里创建的 ca.crt 是会放到服务端的，接下来我们创建安装在客户端的证书。&lt;/p&gt;

&lt;h3 id=&quot;创建客户端证书&quot;&gt;创建客户端证书&lt;/h3&gt;

&lt;p&gt;首先是创建证书请求：&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;openssl genrsa &lt;span class=&quot;nt&quot;&gt;-out&lt;/span&gt; client.key 4096
openssl req &lt;span class=&quot;nt&quot;&gt;-new&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-key&lt;/span&gt; client.key &lt;span class=&quot;nt&quot;&gt;-out&lt;/span&gt; client.csr
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;这里同样会需要输入一些信息，看着输入就好。&lt;/p&gt;

&lt;p&gt;然后使用 ca 证书进行签发：&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;openssl x509 &lt;span class=&quot;nt&quot;&gt;-req&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-days&lt;/span&gt; 365 &lt;span class=&quot;nt&quot;&gt;-in&lt;/span&gt; client.csr &lt;span class=&quot;nt&quot;&gt;-CA&lt;/span&gt; ca.crt &lt;span class=&quot;nt&quot;&gt;-CAkey&lt;/span&gt; ca.key &lt;span class=&quot;nt&quot;&gt;-set_serial&lt;/span&gt; 01 &lt;span class=&quot;nt&quot;&gt;-out&lt;/span&gt; client.crt
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;这样就签发好了客户端证书 client.crt。&lt;/p&gt;

&lt;h3 id=&quot;安装客户端证书&quot;&gt;安装客户端证书&lt;/h3&gt;

&lt;p&gt;上一步产生的 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;client.crt&lt;/code&gt; 并不能直接安装，需要转成 PKCS 格式：&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;openssl pkcs12 &lt;span class=&quot;nt&quot;&gt;-export&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-clcerts&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-in&lt;/span&gt; client.crt &lt;span class=&quot;nt&quot;&gt;-inkey&lt;/span&gt; client.key &lt;span class=&quot;nt&quot;&gt;-out&lt;/span&gt; client.p12
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;这里的 client.p12 在 MacOS 上就可以直接双击安装到系统的 KeyChain 里面。&lt;/p&gt;

&lt;h3 id=&quot;部署到服务器&quot;&gt;部署到服务器&lt;/h3&gt;

&lt;p&gt;服务端只需要一个 ca.crt 文件即可。&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;scp sa.crt somebody@someserver:/etc/nginx/certs/client-auth-ca.crt
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Nginx 中加入如下的配置：&lt;/p&gt;

&lt;div class=&quot;language-nginx highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;server&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;kn&quot;&gt;...&lt;/span&gt;
    &lt;span class=&quot;s&quot;&gt;ssl_client_certificate&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;/etc/nginx/certs/client-auth-ca.crt&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;kn&quot;&gt;ssl_verify_client&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;optional&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;kn&quot;&gt;...&lt;/span&gt;
&lt;span class=&quot;err&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;其中 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ssl_verify_client&lt;/code&gt; 的值可以是 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;optional | on&lt;/code&gt; 者两个：&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;当选择 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;on&lt;/code&gt; 时会强制进行客户端认证，失败无法访问；&lt;/li&gt;
  &lt;li&gt;当选择 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;optional&lt;/code&gt; 的时候，认证是可选的，是否认证成功可以从 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;$ssl_client_verify&lt;/code&gt; 变量得知。&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;比如，我们想要网站根目录是任意访问的，但是 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/admin&lt;/code&gt; 路径下是需要认证才能访问的，就可以这么配置：&lt;/p&gt;

&lt;div class=&quot;language-nginx highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;server&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;kn&quot;&gt;...&lt;/span&gt;
    &lt;span class=&quot;s&quot;&gt;ssl_client_certificate&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;/etc/nginx/certs/client-auth-ca.crt&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;kn&quot;&gt;ssl_verify_client&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;optional&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;kn&quot;&gt;...&lt;/span&gt;

    &lt;span class=&quot;s&quot;&gt;location&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;/admin&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;kn&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$ssl_client_verify&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;!=&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;SUCCESS)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;kn&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;401&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
        &lt;span class=&quot;kn&quot;&gt;proxy_pass&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;http://localhost:5000&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;完成&quot;&gt;完成&lt;/h2&gt;

&lt;p&gt;当打开网站且本机装有对应的客户端证书时，就会出现请求证书认证的提示框：&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/fs/17-08-03-704063d1.png&quot; alt=&quot;cert-2&quot; /&gt;&lt;/p&gt;

&lt;p&gt;点确定就可以通过认证啦。&lt;/p&gt;

&lt;h2 id=&quot;参考&quot;&gt;参考&lt;/h2&gt;

&lt;p&gt;&lt;a href=&quot;https://gist.github.com/mtigas/952344&quot;&gt;ClientSide SSL&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;http://nginx.org/en/docs/http/ngx_http_ssl_module.html&quot;&gt;Nginx SSL Module&lt;/a&gt;&lt;/p&gt;</content><author><name></name></author><category term="SSL" /><summary type="html">对于自己的个人网站，安全要求没有那么重要。如果想对网站做一个简单的身份验证，可以有两种方式：</summary></entry><entry><title type="html">Alembic 添加不能为空的列</title><link href="/2014/08/14/alembic-add-not-nullable-column.html" rel="alternate" type="text/html" title="Alembic 添加不能为空的列" /><published>2014-08-14T18:01:00+08:00</published><updated>2014-08-14T18:01:00+08:00</updated><id>/2014/08/14/alembic-add-not-nullable-column</id><content type="html" xml:base="/2014/08/14/alembic-add-not-nullable-column.html">&lt;p&gt;当我们对一个已存在数据的Table中添加一个&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;nullable=False&lt;/code&gt;的列时，通常&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;alembic&lt;/code&gt;会直接报错，因为它不知道要给这个列赋予什么值，所以会默认给个&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;NULL&lt;/code&gt;的值，但是这个值与&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;nullable=False&lt;/code&gt;的规则冲突了。&lt;/p&gt;

&lt;p&gt;通常这种不能为空的列都是应该有一个默认值的，所以可以直接在alembic中指定这个列的默认值：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;:::python
op.add_column('callroom', sa.Column('consulvalid', sa.Integer(), nullable=True, server_default=text(&quot;0&quot;)))
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;但也有可能这个默认值只想在Python的&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;orm&lt;/code&gt;环境中定义，不想定义到数据库中，比如&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;:::python
class MyClass(Base):
    name = Column(Unicode(40), default=u'hello')
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;这种情况下，可以这么修改alembic迁移脚本：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;:::python
from sqlalchemy.sql import table, column
op.add_column('callroom', sa.Column('consulvalid', sa.Boolean(), nullable=True))
callroom = table(&quot;callroom&quot;, column(&quot;consulvalid&quot;, sa.Boolean()))
op.execute(callroom.update().values(consulvalid=False))
op.alter_column(&quot;callroom&quot;, &quot;consulvalid&quot;, nullable=False)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;即先增加了一个&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;nullable=True&lt;/code&gt;的列，然后将这个列设置默认值后，再改成&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;nullable=False&lt;/code&gt;。&lt;/p&gt;</content><author><name></name></author><category term="alembic" /><category term="sqlalchemy" /><category term="python" /><category term="数据库" /><summary type="html">当我们对一个已存在数据的Table中添加一个nullable=False的列时，通常alembic会直接报错，因为它不知道要给这个列赋予什么值，所以会默认给个NULL的值，但是这个值与nullable=False的规则冲突了。</summary></entry><entry><title type="html">SqlAlchemy 标记字段变更</title><link href="/2014/06/16/sqlalchemy-set-column-modified.html" rel="alternate" type="text/html" title="SqlAlchemy 标记字段变更" /><published>2014-06-16T19:59:00+08:00</published><updated>2014-06-16T19:59:00+08:00</updated><id>/2014/06/16/sqlalchemy-set-column-modified</id><content type="html" xml:base="/2014/06/16/sqlalchemy-set-column-modified.html">&lt;p&gt;为了在数据库里面存储一些比较随意的结构化数据，我参照(抄袭)&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;sqlalchemy&lt;/code&gt;的文档实现了一个自定义类型：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;class JSON(TypeDecorator):
    &quot;&quot;&quot; Json String Field &quot;&quot;&quot;
    impl = Text

    def process_bind_param(self, value, dialect):
        if value is None:
            return value
        elif isinstance(value, (dict, list)):
            return json.dumps(value)
        else:
            raise ValueError(&quot;unsupported value type&quot;)

    def process_result_value(self, value, dialect):
        if value is None:
            return value
        else:
            return json.loads(value)


class MyModel(Base):
    ...
    json = Column(JSON, default=[])

    def add_item(self, item):
        self.json.append(item)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;其实这本质上就是把python的&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;dict&lt;/code&gt;或&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;list&lt;/code&gt;直接dumps成json内容存入数据库，从数据库读出之后再自动&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;loads&lt;/code&gt;成&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;dict&lt;/code&gt;或者&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;list&lt;/code&gt;来使用。&lt;/p&gt;

&lt;p&gt;但是再使用的过程中发现了一个问题。对这个&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;MyModel&lt;/code&gt;的实例无论怎么修改其json字段都无法正常保存到数据库中，目测应该是SA没有检测到这个字段的变化而导致的。重新翻看文档，发现文档里面已经有说明了。&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;Note that the ORM by default will not detect “mutability” on such a type - meaning, in-place changes to values will not be detected and will not be flushed. Without further steps, you instead would need to replace the existing value with a new one on each parent object to detect changes. Note that there’s nothing wrong with this, as many applications may not require that the values are ever mutated once created. For those which do have this requirement, support for mutability is best applied using the sqlalchemy.ext.mutable extension - see the example in Mutation Tracking.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;再看看&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;sqlalchemy.ext.mutable&lt;/code&gt;模块的文档，发现使用起来也没有那么简练，而我只想找个方法来主动标记字段变更就行了。于是翻看&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;mutable&lt;/code&gt;模块的代码，找到了其最重要的一句：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;from sqlalchemy.orm.attributes import flag_modified

class Mutable(MutableBase):

    def changed(self):
        &quot;&quot;&quot;Subclasses should call this method whenever change events occur.&quot;&quot;&quot;

        for parent, key in self._parents.items():
            flag_modified(parent, key)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;其实就是这个&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;flag_modified&lt;/code&gt;标记了修改状态，所以只需要把我自己的代码改一句就行了。&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;from sqlalchemy.orm.attributes import flag_modified

class MyModel(Base):
    ...
    json = Column(JSON, default=[])

    def add_item(self, item):
        self.json.append(item)
        flag_modified(self, &quot;json&quot;)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;打完收工。&lt;/p&gt;</content><author><name></name></author><category term="python" /><category term="sqlalchemy" /><category term="database" /><summary type="html">为了在数据库里面存储一些比较随意的结构化数据，我参照(抄袭)sqlalchemy的文档实现了一个自定义类型：</summary></entry><entry><title type="html">知识在于积累？知识在于经历</title><link href="/2014/06/06/knowledge-and-experience.html" rel="alternate" type="text/html" title="知识在于积累？知识在于经历" /><published>2014-06-06T15:02:00+08:00</published><updated>2014-06-06T15:02:00+08:00</updated><id>/2014/06/06/knowledge-and-experience</id><content type="html" xml:base="/2014/06/06/knowledge-and-experience.html">&lt;p&gt;今天折腾代码的时候突然有了感悟，也可以说是自己给自己找到的合理的安慰。&lt;/p&gt;

&lt;p&gt;我的工作就是在实现各种各样的功能，解决各种各样的问题，所以在这个过程中查阅了无数的资料，用到了无数的技术。&lt;/p&gt;

&lt;p&gt;然后，有一天，在寻找一个问题的解决方案的时候，我发现这个问题我以前经历过的，但是现在忘了。于是，只能重新重新在网络上查阅资料。&lt;/p&gt;

&lt;p&gt;每当遇到这样的情况的时候我都会很郁闷，感觉这本来是自己已经积累的知识就这么“挥发”了，只能重新积累，等待什么时候再次“挥发”了。&lt;/p&gt;

&lt;p&gt;现在这种情况遇到的越来越频繁，让我觉得自己在各种“重复过程“中浪费时间。但我知道事情并不是这样的，只是缺少对这种现象的一种”正确“的看待方式。&lt;/p&gt;

&lt;p&gt;于是，标题的文字就这么出来了。&lt;/p&gt;

&lt;p&gt;真正的知识并不是对一种技术的熟悉，并不是对某种方案的实现细节的记忆，正真的知识应该是”对这个世界的理解“。当我们对这个世界真正理解之后，所有的”方案“和”技术“等玩意在需要的时候就会不自觉的出现了。&lt;/p&gt;

&lt;p&gt;为什么说是”不自觉“的出现呢，因为它可能是在我花了不到一分钟的时间的”Google“后出现，可能是在我花了一分钟时间了解了某个技术的大体框架之后立刻就”猜“出了它的详细用法，甚至是花了一分钟时间思考之后就设计出了”完美“的方案。&lt;/p&gt;

&lt;p&gt;而这种知识就是在我为之苦恼的”重复过程“中经历而来的。&lt;/p&gt;

&lt;p&gt;所以，你的一切努力都没有白费。&lt;/p&gt;</content><author><name></name></author><category term="essay" /><category term="thought" /><summary type="html">今天折腾代码的时候突然有了感悟，也可以说是自己给自己找到的合理的安慰。</summary></entry><entry><title type="html">SQLAlchemy 中的级联删除</title><link href="/2014/03/24/sqlalchemy-cascade-delete.html" rel="alternate" type="text/html" title="SQLAlchemy 中的级联删除" /><published>2014-03-24T16:13:00+08:00</published><updated>2014-03-24T16:13:00+08:00</updated><id>/2014/03/24/sqlalchemy-cascade-delete</id><content type="html" xml:base="/2014/03/24/sqlalchemy-cascade-delete.html">&lt;p&gt;又被一个问题折腾了半天，找到原因，记录下来。&lt;/p&gt;

&lt;h2 id=&quot;问题描述&quot;&gt;问题描述&lt;/h2&gt;

&lt;p&gt;使用&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;SQLAlchemy&lt;/code&gt;建立一对多的&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;relationship&lt;/code&gt;：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;class Parent(db.Model):
    __tablename__ = &quot;parent&quot;
    id = db.Column(db.Integer, primary_key=True)
    name = db.Column(db.Text)


class Child(db.Model):
    __tablename__ = &quot;child&quot;
    id = db.Column(db.Integer, primary_key=True)

    parent_id = db.Column(db.Integer,
                          db.ForeignKey(&quot;parent.id&quot;, ondelete=&quot;CASCADE&quot;),
                          nullable=False)
    parent = db.relationship(&quot;Parent&quot;, backref=&quot;children&quot;)

    name = db.Column(db.Text)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;先增加一个&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;parent&lt;/code&gt;和其下的两个&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;child&lt;/code&gt;，然后删除这个&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;parent&lt;/code&gt;&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;#增加记录
p = Parent(name=&quot;P1&quot;)
p.children.append(Child(name=&quot;C1&quot;))
p.children.append(Child(name=&quot;C2&quot;))
db.session.add(p)
db.session.commit()

#删除记录
db.session.delete(p)
db.session.commit()
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;报错：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;BEGIN (implicit)
SELECT parent.id AS parent_id, parent.name AS parent_name
FROM parent
WHERE parent.id = ?
(1,)
SELECT child.id AS child_id, child.parent_id AS child_parent_id, child.name AS child_name
FROM child
WHERE ? = child.parent_id
(1,)
UPDATE child SET parent_id=? WHERE child.id = ?
(None, 1)
ROLLBACK
Traceback (most recent call last):
  File &quot;main.py&quot;, line 44, in &amp;lt;module&amp;gt;
    test()
  File &quot;main.py&quot;, line 41, in test
    db.session.commit()
  ……
  File &quot;build/bdist.macosx-10.4-x86_64/egg/sqlalchemy/engine/default.py&quot;, line 388, in do_execute
sqlalchemy.exc.IntegrityError: (IntegrityError) child.parent_id may not be NULL u'UPDATE child SET parent_id=? WHERE child.id = ?' (None, 1)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;很明显错误原因是由于&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;sa&lt;/code&gt;在处理删除&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;parent&lt;/code&gt;的时候产生了&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;'UPDATE child SET parent_id=? WHERE child.id = ?' (None, 1)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;这样的SQL语句。而&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;child&lt;/code&gt;的外键设置里面明确有&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;nullable=False&lt;/code&gt;，所以提交失败。&lt;/p&gt;

&lt;h2 id=&quot;问题解决&quot;&gt;问题解决&lt;/h2&gt;

&lt;p&gt;我之前一直以为当我在外键设置里面有&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ondelete=&quot;CASCADE&quot;&lt;/code&gt;这一条之后，&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;sa&lt;/code&gt;应该会自动产生级联的删除命令，或者只产生单独对&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;parent&lt;/code&gt;的删除SQL，让数据库自己产生对&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;child&lt;/code&gt;的删除。可事实是&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;sa&lt;/code&gt;产生了&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;SET NULL&lt;/code&gt;这样的SQL，为什么呢。&lt;/p&gt;

&lt;p&gt;网上搜索未果，再从官方文档找答案，终于找到了。&lt;/p&gt;

&lt;h3 id=&quot;foreignkeyondelete&quot;&gt;ForeignKey(..ondelete)&lt;/h3&gt;

&lt;p&gt;这里的&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ondelete&lt;/code&gt;控制的只是创建数据库Schema时候的ONDELETE规则，不会影响运行中&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;sa&lt;/code&gt;如何产生SQL命令。&lt;/p&gt;

&lt;h3 id=&quot;relationshipcascade&quot;&gt;relationship(…cascade)&lt;/h3&gt;

&lt;p&gt;这里参数参数才会影响&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;sa&lt;/code&gt;如何级联产生SQL语句，默认值为&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;save-update, merge&lt;/code&gt;，这就是罪魁祸首。&lt;/p&gt;

&lt;p&gt;把它设置成&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;all, delete-orphan&lt;/code&gt;之后，&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;sa&lt;/code&gt;就会产生级联的删除命令，而不是&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;set null&lt;/code&gt;命令了。&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;parent = db.relationship(&quot;Parent&quot;, backref=db.backref(&quot;children&quot;,  cascade=&quot;all, delete-orphan&quot;))
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;输出：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;BEGIN (implicit)
SELECT parent.id AS parent_id, parent.name AS parent_name
FROM parent
WHERE parent.id = ?
(1,)
SELECT child.id AS child_id, child.parent_id AS child_parent_id, child.name AS child_name
FROM child
WHERE ? = child.parent_id
(1,)
DELETE FROM child WHERE child.id = ?
((1,), (2,))
DELETE FROM parent WHERE parent.id = ?
(1,)
COMMIT
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;cascade还有很多其他选项，可具体查看文档。&lt;/p&gt;

&lt;h3 id=&quot;relationshippassive_deletes&quot;&gt;relationship(…passive_deletes)&lt;/h3&gt;

&lt;p&gt;这是另一种可选的解决方案。&lt;/p&gt;

&lt;p&gt;如果想完全靠数据库的&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ONDELETE&lt;/code&gt;规则在自动删除数据，应该将该&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;passive_deletes&lt;/code&gt;置为True，这样就不会在删除的时候产生多余的SQL命令了。&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;parent = db.relationship(&quot;Parent&quot;, backref=db.backref(&quot;children&quot;,  passive_deletes=True))
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;输出:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;SELECT parent.id AS parent_id, parent.name AS parent_name
FROM parent
WHERE parent.id = ?
(1,)
DELETE FROM parent WHERE parent.id = ?
(1,)
COMMIT
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;</content><author><name></name></author><category term="sqlalchemy" /><category term="python" /><category term="database" /><summary type="html">又被一个问题折腾了半天，找到原因，记录下来。</summary></entry><entry><title type="html">Python datetime and timezone</title><link href="/2014/03/21/python-datetime-and-timezone.html" rel="alternate" type="text/html" title="Python datetime and timezone" /><published>2014-03-21T15:17:00+08:00</published><updated>2014-03-21T15:17:00+08:00</updated><id>/2014/03/21/python-datetime-and-timezone</id><content type="html" xml:base="/2014/03/21/python-datetime-and-timezone.html">&lt;p&gt;悲剧的发现之前一直都理解有误啊，仔细读了一遍文档，这里做个记录。&lt;/p&gt;

&lt;h2 id=&quot;基本类型&quot;&gt;基本类型&lt;/h2&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;datetime&lt;/code&gt;库里面主要有这么五种数据：&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;date&lt;/li&gt;
  &lt;li&gt;datetime&lt;/li&gt;
  &lt;li&gt;time&lt;/li&gt;
  &lt;li&gt;timedelta&lt;/li&gt;
  &lt;li&gt;tzinfo&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;date&quot;&gt;date&lt;/h3&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;date&lt;/code&gt;表示纯粹的日期数据，即“2020-02-21”这种，不含时区概念。&lt;/p&gt;

&lt;p&gt;文档里面有这么一句：&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;January 1 of year 1 is called day number 1, January 2 of year 1 is called day number 2, and so on.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;在加上&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;date.fromordinal&lt;/code&gt;和&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;date.toorinal&lt;/code&gt;这两个方法，我觉得其内部可能就是用&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;day number&lt;/code&gt;这个整型数来表示的。不过这个无所谓啦。&lt;/p&gt;

&lt;h3 id=&quot;time&quot;&gt;time&lt;/h3&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;time&lt;/code&gt;表示时间数据，即”12:22:34”这种，但是它是&lt;strong&gt;可以&lt;/strong&gt;有时区概念&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;time.tzinfo&lt;/code&gt;在里面的。&lt;/p&gt;

&lt;p&gt;关于Time Zone的东西下面会详细说，这里先不说了。&lt;/p&gt;

&lt;h3 id=&quot;datetime&quot;&gt;datetime&lt;/h3&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;datetime&lt;/code&gt;是&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;date&lt;/code&gt;和&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;time&lt;/code&gt;的结合体，表示完整的时间概念，即”2014-02-21 12:23:23”这种。也是&lt;strong&gt;可以&lt;/strong&gt;有时区概念的。&lt;/p&gt;

&lt;p&gt;另外，&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;datetime&lt;/code&gt;是&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;date&lt;/code&gt;的子类。&lt;/p&gt;

&lt;h3 id=&quot;timedelta&quot;&gt;timedelta&lt;/h3&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;timedelta&lt;/code&gt;表示时间长度（时间差），很好理解，没啥好说的。&lt;/p&gt;

&lt;h3 id=&quot;tzinfo&quot;&gt;tzinfo&lt;/h3&gt;

&lt;p&gt;即Time Zone Info，时区信息。这个原始的&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;tzinfo&lt;/code&gt;是一个抽象基类，不应该直接实例化，应该子类化并实现一些必要函数再使用。&lt;/p&gt;

&lt;p&gt;需要实现的函数如下：&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;tzinfo.utcoffset(self, dt)&lt;/li&gt;
  &lt;li&gt;tzinfo.dst(self, dt)&lt;/li&gt;
  &lt;li&gt;tzinfo.tzname(self, dt)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;待实现的这些函数主要都是用于被&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;datetime&lt;/code&gt;或者&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;time&lt;/code&gt;实例调用。逻辑会在下面说明。&lt;/p&gt;

&lt;h2 id=&quot;关于时区&quot;&gt;关于时区&lt;/h2&gt;

&lt;p&gt;上面说到&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;datetime&lt;/code&gt;和&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;time&lt;/code&gt;是&lt;strong&gt;可以&lt;/strong&gt;有时区概念的，为什么是&lt;strong&gt;可以&lt;/strong&gt;有呢？&lt;/p&gt;

&lt;p&gt;先从文档里面直接拿出两个名词：&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;There are two kinds of date and time objects: “naive” and “aware”.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;“Naive” Object表示单纯的时间数据，不包含时区信息，所以无法直接进行时区转换等操作。&lt;/p&gt;

&lt;p&gt;“Aware” Object表示包含了时区信息的时间数据，这其实才是真实世界中可以在时间轴上找到自己位置的对象。由于带有了时区信息，所以也就原生支持了各时区转换等操作。&lt;/p&gt;

&lt;p&gt;同样是一个&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;datetime&lt;/code&gt;(或者&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;time&lt;/code&gt;)对象，当&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;datetime.tzinfo is None&lt;/code&gt;时它就是”naive”的，否则&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;datetime.tzinfo&lt;/code&gt;存放了时区信息，它就是”aware”的。&lt;/p&gt;

&lt;p&gt;让一个naive对象变成aware对象最直接的方法就是给它设置一个&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;tzinfo&lt;/code&gt;对象：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;n = datetime.now()
# 注意，replace方法只会直接修改属性值，不会自动根据时区做时间调整
n.replace(tzinfo=sometz)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;如果对一个naive对象调用一些跟时区相关的方法(如: &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;datetime.astimezone&lt;/code&gt;)，就会报错或无效，因为这些方案的实现中通常需要使用&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;datetime.tzinfo&lt;/code&gt;中实现的方法。&lt;/p&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;tzinfo&lt;/code&gt;只是一个抽象基类，定义了一些子类化时候需要实现的方案，在python的标准库里面没有任何实现。&lt;/p&gt;

&lt;p&gt;可以为什么呢？标准库里面不应该有一些基本的通用的实现么。猜测估计是由于当前世界上各时区的混乱（还有夏令时这种奇葩的东西），以及各平台下时区信息的存储方式的差异，导致不适合在标准库里面实现吧。&lt;/p&gt;

&lt;p&gt;实现一个最简单的UTC Time Zone如下：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;from datetime import tzinfo, timedelta

ZERO = timedelta(0)

class UTC(tzinfo):
    # 本时区与UTC时间的差值
    def utcoffset(self, dt):
        return ZERO

    # 时区名称
    def tzname(self, dt):
        return “UTC”

    # 夏令时
    def dst(self, dt):
        return ZERO
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;当然也没有必要真的就自己手动实现，使用&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;pytz&lt;/code&gt;或者&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;dateutils&lt;/code&gt;这些第三方库就好了。&lt;/p&gt;

&lt;h2 id=&quot;关于时间戳timestamp&quot;&gt;关于时间戳(TimeStamp)&lt;/h2&gt;

&lt;p&gt;时间戳的含义就是UTC时间1970-01-01 00:00:00到当前时间的秒数，这本质上是一个时间差，所以没有时区的概念。&lt;/p&gt;

&lt;p&gt;对于时间戳的操作，主要涉及的是另一个标准库&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;time&lt;/code&gt;，注意这里的&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;time&lt;/code&gt;并不是&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;datetime.time&lt;/code&gt;。&lt;/p&gt;

&lt;p&gt;获取当前时间戳:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;import time
current_timestamp = time.time()
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;使用naive对象&quot;&gt;使用Naive对象&lt;/h2&gt;

&lt;p&gt;标准库里面没有实现&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;tzinfo&lt;/code&gt;，所以如果我们没有使用第三方库，那么我们一定都是在使用&lt;em&gt;Naive&lt;/em&gt;对象了。&lt;/p&gt;

&lt;p&gt;但是，实际情况是即时我们使用的都是&lt;em&gt;Naive&lt;/em&gt;对象，还是会有Local时间和UTC时间的区分！这就是我之前一直很混乱的地方。&lt;/p&gt;

&lt;p&gt;其实，如果只需要做本地时间和UTC时间的转换是用不着&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;tzinfo&lt;/code&gt;的。&lt;/p&gt;

&lt;h3 id=&quot;datetimenowtzinfo&quot;&gt;datetime.now([tzinfo])&lt;/h3&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;datetime.now&lt;/code&gt;是一个类函数，返回表示当前时间的&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;datetime&lt;/code&gt;对象。它有一个可选参数，可传入一个具体的时区信息来返回指定时区的当前时间。如果不带参数调用，那么它会返回本地时间的&lt;em&gt;naive&lt;/em&gt;对象。&lt;/p&gt;

&lt;p&gt;比如，我现在是北京时间&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;2014-03-21 14:11:00&lt;/code&gt;:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&amp;gt;&amp;gt;&amp;gt; from datetime import datetime
&amp;gt;&amp;gt;&amp;gt; now = datetime.now()
&amp;gt;&amp;gt;&amp;gt; print now
2014-03-21 14:11:23.414917
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;虽然返回的是本地时间（北京时间），但是它是&lt;em&gt;naive&lt;/em&gt;对象，不带时区信息！&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&amp;gt;&amp;gt;&amp;gt; print now.tzinfo
None
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;如果想要得到UTC时间:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&amp;gt;&amp;gt;&amp;gt; utcnow = datetime.utcnow()
&amp;gt;&amp;gt;&amp;gt; print utcnow
2014-03-21 06:16:10.025043
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;datetime--timestamp&quot;&gt;datetime &amp;lt;=&amp;gt; timestamp&lt;/h3&gt;

&lt;p&gt;本地时间和时间戳之间转换:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&amp;gt;&amp;gt;&amp;gt; now = datetime.now()
&amp;gt;&amp;gt;&amp;gt; print now
2014-03-21 14:22:11
&amp;gt;&amp;gt;&amp;gt; ts = time.mktime(now.timetuple())
&amp;gt;&amp;gt;&amp;gt; print ts
1395382931.0
&amp;gt;&amp;gt;&amp;gt; _now = datetime.fromtimestamp(ts)
&amp;gt;&amp;gt;&amp;gt; print _now
2014-03-21 14:22:11
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;如果想把时间戳转成UTC时间:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&amp;gt;&amp;gt;&amp;gt; _utcnow = datetime.utcfromtimestamp(ts)
&amp;gt;&amp;gt;&amp;gt; print _utcnow
2014-03-21 06:22:11
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;如果想把UTC时间转成时间戳:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&amp;gt;&amp;gt;&amp;gt; ts = time.mktimefromutc(datetime.utcnow().timetuple())
Traceback (most recent call last):
  File &quot;&amp;lt;stdin&amp;gt;&quot;, line 1, in &amp;lt;module&amp;gt;
AttributeError: 'module' object has no attribute 'mktimefromutc'
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;好吧，总觉得&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;time&lt;/code&gt;里面缺少这这么个函数，不过我们可以这么来：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;utcnow = datetime.utcnow()
ts_from_utcdt = time.mktime(utcnow.timetuple()) - time.timezone
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;或者:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;import calendar
ts_from_utcdt = calendar.timegm(utcnow.timetuple())
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;又用到了&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;calendar&lt;/code&gt;这个库，真够乱的。&lt;/p&gt;

&lt;h3 id=&quot;local-datetime--utc-datetime&quot;&gt;Local DateTime &amp;lt;=&amp;gt; UTC DateTime&lt;/h3&gt;

&lt;p&gt;对于naive对象，貌似没有方法能够直接支持本地时间和UTC时间转换的，所以只能从TimeStamp中绕一圈了。&lt;/p&gt;

&lt;p&gt;Local DateTime =&amp;gt; UTC DateTime&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&amp;gt;&amp;gt;&amp;gt; print now
2014-03-21 14:41:42.529159
&amp;gt;&amp;gt;&amp;gt; ts = time.mktime(now.timetuple())
&amp;gt;&amp;gt;&amp;gt; utcnow = datetime.utcfromtimestamp(ts)
&amp;gt;&amp;gt;&amp;gt; print utcnow
2014-03-21 06:41:42
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;UTC DateTime =&amp;gt; Local DateTime&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&amp;gt;&amp;gt;&amp;gt; utc = datetime.utcnow()
&amp;gt;&amp;gt;&amp;gt; print utc
2014-03-21 06:44:15.069810
&amp;gt;&amp;gt;&amp;gt; ts = time.mktime(utc.timetuple()) - time.timezone
&amp;gt;&amp;gt;&amp;gt; local = datetime.fromtimestamp(ts)
&amp;gt;&amp;gt;&amp;gt; print local
2014-03-21 14:44:15
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;使用aware对象&quot;&gt;使用Aware对象&lt;/h2&gt;

&lt;p&gt;如果使用&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Aware&lt;/code&gt;对象，一些貌似都清晰了不少。&lt;/p&gt;

&lt;h3 id=&quot;datetimenowtzinfo-1&quot;&gt;datetime.now([tzinfo])&lt;/h3&gt;

&lt;p&gt;我们可以返回一个指定时区的&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;datetime&lt;/code&gt;&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&amp;gt;&amp;gt;&amp;gt; from datetime import datetime
&amp;gt;&amp;gt;&amp;gt; import pytz
&amp;gt;&amp;gt;&amp;gt; tz_cn = pytz.timezone(&quot;Asia/Shanghai&quot;)
&amp;gt;&amp;gt;&amp;gt; tz_us = pytz.timezone('America/Los_Angeles')
&amp;gt;&amp;gt;&amp;gt; print datetime.now(tz_cn)
2014-03-21 14:58:38.059945+08:00
&amp;gt;&amp;gt;&amp;gt; print datetime.now(tz_us)
2014-03-20 23:58:45.427634-07:00
&amp;gt;&amp;gt;&amp;gt; print datetime.now(pytz.UTC)
2014-03-21 06:58:56.507517+00:00
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;并且它是&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;aware&lt;/code&gt;的&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&amp;gt;&amp;gt;&amp;gt; dt_cn = datetime.now(tz_cn)
&amp;gt;&amp;gt;&amp;gt; dt_cn.tzinfo
&amp;lt;DstTzInfo 'Asia/Shanghai' CST+8:00:00 STD&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;时区转换&quot;&gt;时区转换&lt;/h3&gt;

&lt;p&gt;对于&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;aware&lt;/code&gt;对象之间的转换很简单:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&amp;gt;&amp;gt;&amp;gt; dt_cn = datetime.now(tz_cn)
&amp;gt;&amp;gt;&amp;gt; print dt_cn
2014-03-21 15:04:21.700744+08:00
&amp;gt;&amp;gt;&amp;gt; dt_us = dt_cn.astimezone(tz_us)
&amp;gt;&amp;gt;&amp;gt; print dt_us
2014-03-21 00:04:21.700744-07:00
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;对于非&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;aware&lt;/code&gt;的对象，可以先将其转换成&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;aware&lt;/code&gt;的再操作：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&amp;gt;&amp;gt;&amp;gt; dt_naive = datetime.utcnow()
&amp;gt;&amp;gt;&amp;gt; print dt_naive
2014-03-21 07:05:58.194690
&amp;gt;&amp;gt;&amp;gt; dt_utc = dt_naive.replace(tzinfo=pytz.UTC)
&amp;gt;&amp;gt;&amp;gt; print dt_utc
2014-03-21 07:05:58.194690+00:00
&amp;gt;&amp;gt;&amp;gt; dt_cn = dt_utc.astimezone(tz_cn)
&amp;gt;&amp;gt;&amp;gt; print dt_cn
2014-03-21 15:05:58.194690+08:00
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;对于&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;aware&lt;/code&gt;对象和时间戳的转换，我觉得还是全都转成UTC时间再操作吧。&lt;/p&gt;</content><author><name></name></author><category term="python" /><category term="datetime" /><category term="timezone" /><summary type="html">悲剧的发现之前一直都理解有误啊，仔细读了一遍文档，这里做个记录。</summary></entry><entry><title type="html">纠结的Many-Many关联</title><link href="/2014/01/23/sqlalchemy-many-many-relationship.html" rel="alternate" type="text/html" title="纠结的Many-Many关联" /><published>2014-01-23T21:00:00+08:00</published><updated>2014-01-23T21:00:00+08:00</updated><id>/2014/01/23/sqlalchemy-many-many-relationship</id><content type="html" xml:base="/2014/01/23/sqlalchemy-many-many-relationship.html">&lt;p&gt;使用了&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;SQLAlchemy&lt;/code&gt;挺长时间的了，之前在使用的过程中一直有一件事挺纠结的，SA到底会不会在commit的使用自动对比多对多关系的变更生成合适的SQL去更新数据库呢？&lt;/p&gt;

&lt;p&gt;现决定做具体实验，搞清楚。&lt;/p&gt;

&lt;h2 id=&quot;数据定义&quot;&gt;数据定义&lt;/h2&gt;

&lt;p&gt;数据定义如下:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;:::python
class Right(Base):
    &quot;&quot;&quot; 权限表 &quot;&quot;&quot;
    __tablename__ = &quot;right&quot;
    name = Column(String(16), primary_key=True)

    def __repr__(self):
        return &quot;&amp;lt;Right(name=%s)&amp;gt;&quot; % self.name


user_right = Table(
    &quot;user_right&quot;, Base.metadata,
    Column(&quot;user_id&quot;, Integer, ForeignKey(&quot;user.id&quot;, ondelete=&quot;CASCADE&quot;)),
    Column(&quot;right&quot;, String(16), ForeignKey(&quot;right.name&quot;, ondelete=&quot;CASCADE&quot;))
)


class User(Base):
    __tablename__ = &quot;user&quot;

    id = Column(Integer, primary_key=True)
    name = Column(String(100), unique=True)
    age = Column(Integer, nullable=False)
    rights = relationship(Right, secondary=user_right)

    def __repr__(self):
        return &quot;&amp;lt;User(name=%s)&amp;gt;&quot; % self.name
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;添加关联&quot;&gt;添加关联&lt;/h2&gt;

&lt;p&gt;当得到了&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;user&lt;/code&gt;实例后，如果新增一个关联的&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;right&lt;/code&gt;:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;:::python
user.rights.append(newright)
session.commit()
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;正常识别出了新增的关联并且生成了合适的&lt;em&gt;SQL&lt;/em&gt;。&lt;/p&gt;

&lt;h2 id=&quot;减少关联&quot;&gt;减少关联&lt;/h2&gt;

&lt;p&gt;如果减少一个关联:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;:::python
user.rights.remove(someright)
session.commit()
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;正常识别出了新增的关联并且生成了合适的&lt;em&gt;SQL&lt;/em&gt;。&lt;/p&gt;

&lt;h2 id=&quot;重置关联&quot;&gt;重置关联&lt;/h2&gt;

&lt;p&gt;这种方式是最值得怀疑的，不再是修改关联，而是重置关联：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;:::python
user.rights = [someright1, someright2]
session.commit()
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;结果是，一切正常运行。看来&lt;em&gt;SA&lt;/em&gt;的确做的很到位了。&lt;/p&gt;

&lt;h2 id=&quot;结论&quot;&gt;结论&lt;/h2&gt;

&lt;p&gt;以后可以不用纠结，直接把对比更新的复杂度丢给&lt;em&gt;SA&lt;/em&gt;去处理就好了。&lt;/p&gt;</content><author><name></name></author><category term="sqlalchemy" /><category term="python" /><category term="postgresql" /><summary type="html">使用了SQLAlchemy挺长时间的了，之前在使用的过程中一直有一件事挺纠结的，SA到底会不会在commit的使用自动对比多对多关系的变更生成合适的SQL去更新数据库呢？</summary></entry><entry><title type="html">关于Gitignore的规则</title><link href="/2014/01/22/about-gitignore.html" rel="alternate" type="text/html" title="关于Gitignore的规则" /><published>2014-01-22T11:00:00+08:00</published><updated>2014-01-22T11:00:00+08:00</updated><id>/2014/01/22/about-gitignore</id><content type="html" xml:base="/2014/01/22/about-gitignore.html">&lt;p&gt;关于&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.gitignore&lt;/code&gt;的语法规则我一直没有很明白，今天决心要弄懂。&lt;/p&gt;

&lt;p&gt;下面的是&lt;strong&gt;Manual Page&lt;/strong&gt;的说明：&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;A blank line matches no files, so it can serve as a separator for readability.&lt;/p&gt;

  &lt;p&gt;A line starting with # serves as a comment. Put a backslash (“&quot;) in front of the first hash for patterns that begin with a hash.&lt;/p&gt;

  &lt;p&gt;An optional prefix “!” which negates the pattern; any matching file excluded by a previous pattern will become included again. If a negated pattern matches, this will override lower precedence patterns sources. Put a backslash (“&quot;) in front of the first “!” for patterns that begin with a literal “!”, for example, “!important!.txt”.&lt;/p&gt;

  &lt;p&gt;If the pattern ends with a slash, it is removed for the purpose of the following description, but it would only find a match with a directory. In other words, foo/ will match a directory foo and paths underneath it, but will not match a regular file or a symbolic link foo (this is consistent with the way how pathspec works in general in git).&lt;/p&gt;

  &lt;p&gt;If the pattern does not contain a slash /, git treats it as a shell glob pattern and checks for a match against the pathname relative to the location of the .gitignore file (relative to the toplevel of the work tree if not from a .gitignore file).&lt;/p&gt;

  &lt;p&gt;Otherwise, git treats the pattern as a shell glob suitable for consumption by fnmatch(3) with the FNM_PATHNAME flag: wildcards in the pattern will not match a / in the pathname. For example, “Documentation/*.html” matches “Documentation/git.html” but not “Documentation/ppc/ppc.html” or “tools/perf/Documentation/perf.html”.&lt;/p&gt;

  &lt;p&gt;A leading slash matches the beginning of the pathname. For example, “/*.c” matches “cat-file.c” but not “mozilla-sha1/sha1.c”.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;我仔细读了半天，发现还是没弄懂，还是亲自做实验吧。&lt;/p&gt;

&lt;p&gt;其实没弄懂的主要是第4，5，6条，一个个解决吧。&lt;/p&gt;

&lt;h2 id=&quot;if-the-pattern-ends-with-a-slash&quot;&gt;If the pattern ends with a slash&lt;/h2&gt;

&lt;p&gt;原文这样：&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;If the pattern ends with a slash, it is removed for the purpose of the following description, but it would only find a match with a directory. In other words, foo/ will match a directory foo and paths underneath it, but will not match a regular file or a symbolic link foo (this is consistent with the way how pathspec works in general in git).&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;正常情况:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;Carl-MBPR ➜  testgitignore git:(master) ✗ cat .gitignore
foo/
Carl-MBPR ➜  testgitignore git:(master) ✗ tree -a -L 1
.
├── .git
├── .gitignore
└── foo
Carl-MBPR ➜  testgitignore git:(master) ✗ git status
# On branch master
# Untracked files:
#   (use &quot;git add &amp;lt;file&amp;gt;...&quot; to include in what will be committed)
#
#   foo
nothing added to commit but untracked files present (use &quot;git add&quot; to track)
Carl-MBPR ➜  testgitignore git:(master) ✗ rm foo
Carl-MBPR ➜  testgitignore git:(master) mkdir foo
Carl-MBPR ➜  testgitignore git:(master) touch foo/file
Carl-MBPR ➜  testgitignore git:(master) tree -L 2
.
└── foo
    └── file

1 directory, 1 file
Carl-MBPR ➜  testgitignore git:(master) git status
# On branch master
nothing to commit, working directory clean
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;子目录中的表现：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;Carl-MBPR ➜  testgitignore git:(master) mkdir somefolder
Carl-MBPR ➜  testgitignore git:(master) touch somefolder/foo
Carl-MBPR ➜  testgitignore git:(master) ✗ git status
# On branch master
# Untracked files:
#   (use &quot;git add &amp;lt;file&amp;gt;...&quot; to include in what will be committed)
#
#   somefolder/
nothing added to commit but untracked files present (use &quot;git add&quot; to track)
Carl-MBPR ➜  testgitignore git:(master) ✗ rm somefolder/foo
Carl-MBPR ➜  testgitignore git:(master) mkdir somefolder/foo
Carl-MBPR ➜  testgitignore git:(master) touch somefolder/foo/file
Carl-MBPR ➜  testgitignore git:(master) git status
# On branch master
nothing to commit, working directory clean
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;结论&lt;/strong&gt;：这里的规则是在所有目录层级中都有效的。&lt;/p&gt;

&lt;h2 id=&quot;if-the-pattern-does-not-contain-a-slash&quot;&gt;If the pattern does not contain a slash&lt;/h2&gt;

&lt;blockquote&gt;
  &lt;p&gt;If the pattern does not contain a slash /, git treats it as a shell glob pattern and checks for a match against the pathname relative to the location of the .gitignore file (relative to the toplevel of the work tree if not from a .gitignore file).&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;实验：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;Carl-MBPR ➜  testgitignore git:(master) cat .gitignore
foo
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;正常情况：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;Carl-MBPR ➜  testgitignore git:(master) touch foo
Carl-MBPR ➜  testgitignore git:(master) git status
# On branch master
nothing to commit, working directory clean

Carl-MBPR ➜  testgitignore git:(master) rm foo
Carl-MBPR ➜  testgitignore git:(master) mkdir foo
Carl-MBPR ➜  testgitignore git:(master) touch foo/file
Carl-MBPR ➜  testgitignore git:(master) tree
.
└── foo
    └── file

1 directory, 1 file
Carl-MBPR ➜  testgitignore git:(master) git status
# On branch master
nothing to commit, working directory clean
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;子目录中表现：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;Carl-MBPR ➜  testgitignore git:(master) mkdir somefolder
Carl-MBPR ➜  testgitignore git:(master) touch somefolder/foo
Carl-MBPR ➜  testgitignore git:(master) tree
.
├── foo
│   └── file
└── somefolder
    └── foo

2 directories, 2 files
Carl-MBPR ➜  testgitignore git:(master) git status
# On branch master
nothing to commit, working directory clean
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;结论&lt;/strong&gt;：这里的规则也是对所有子目录有效。&lt;/p&gt;

&lt;h2 id=&quot;otherwise-git-treats-the-pattern-as-a-shell-glob-suitable-for-consumption-by-fnmatch3-with-the-fnm_pathname-flag&quot;&gt;Otherwise, git treats the pattern as a shell glob suitable for consumption by fnmatch(3) with the FNM_PATHNAME flag&lt;/h2&gt;

&lt;p&gt;原文:&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;Otherwise, git treats the pattern as a shell glob suitable for consumption by fnmatch(3) with the FNM_PATHNAME flag: wildcards in the pattern will not match a / in the pathname. For example, “Documentation/*.html” matches “Documentation/git.html” but not “Documentation/ppc/ppc.html” or “tools/perf/Documentation/perf.html”.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;实验：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;Carl-MBPR ➜  testgitignore git:(master) cat .gitignore
foo/*.file
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;正常情况：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;Carl-MBPR ➜  testgitignore git:(master) mkdir foo
Carl-MBPR ➜  testgitignore git:(master) touch foo/aa
Carl-MBPR ➜  testgitignore git:(master) ✗ git status
# On branch master
# Untracked files:
#   (use &quot;git add &amp;lt;file&amp;gt;...&quot; to include in what will be committed)
#
#   foo/
nothing added to commit but untracked files present (use &quot;git add&quot; to track)
Carl-MBPR ➜  testgitignore git:(master) ✗ mv foo/aa foo/aa.file
Carl-MBPR ➜  testgitignore git:(master) tree
.
└── foo
    └── aa.file

1 directory, 1 file
Carl-MBPR ➜  testgitignore git:(master) git status
# On branch master
nothing to commit, working directory clean
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;子目录中表现：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;Carl-MBPR ➜  testgitignore git:(master) mkdir somefolder
Carl-MBPR ➜  testgitignore git:(master) mkdir somefolder/foo
Carl-MBPR ➜  testgitignore git:(master) touch somefolder/foo/some.file
Carl-MBPR ➜  testgitignore git:(master) ✗ tree
.
├── foo
│   └── aa.file
└── somefolder
    └── foo
        └── some.file

3 directories, 2 files
Carl-MBPR ➜  testgitignore git:(master) ✗ git status
# On branch master
# Untracked files:
#   (use &quot;git add &amp;lt;file&amp;gt;...&quot; to include in what will be committed)
#
#   somefolder/
nothing added to commit but untracked files present (use &quot;git add&quot; to track)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;结论&lt;/strong&gt;：该类型规则不会在子目录中生效。&lt;/p&gt;

&lt;h2 id=&quot;总结&quot;&gt;总结&lt;/h2&gt;

&lt;ol&gt;
  &lt;li&gt;当规则是简单文件（目录）名的匹配的话，会在当前目录和子目录中生效。&lt;/li&gt;
  &lt;li&gt;如果规则中包含的路径（即使不是绝对路径），该规则都只会在当前目录生效。&lt;/li&gt;
  &lt;li&gt;可以使用&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/foo&lt;/code&gt;的形式来从项目根目录开始匹配。&lt;/li&gt;
&lt;/ol&gt;</content><author><name></name></author><category term="git," /><category term="gitignore" /><summary type="html">关于.gitignore的语法规则我一直没有很明白，今天决心要弄懂。</summary></entry></feed>