@@ -6,75 +6,81 @@ Customizing
6
6
===========
7
7
8
8
Flask-SQLAlchemy defines sensible defaults. However, sometimes customization is
9
- needed. Two major pieces to customize are the Model base class and the default
10
- Query class .
9
+ needed. There are various ways to customize how the models are defined and
10
+ interacted with .
11
11
12
- Both of these customizations are applied at the creation of the :class: `SQLAlchemy `
12
+ These customizations are applied at the creation of the :class: `SQLAlchemy `
13
13
object and extend to all models derived from its ``Model `` class.
14
14
15
+
15
16
Model Class
16
17
-----------
17
18
18
- Flask-SQLAlchemy allows defining a custom declarative base, just like SQLAlchemy,
19
- that all model classes should extend from. For example, if all models should have
20
- a custom ``__repr__ `` method::
19
+ SQLAlchemy models all inherit from a declarative base class. This is exposed
20
+ as ``db.Model `` in Flask-SQLAlchemy, which all models extend. This can be
21
+ customized by subclassing the default and passing the custom class to
22
+ ``model_class ``.
21
23
22
- from flask_sqlalchemy import Model # this is the default declarative base
23
- from flask_sqlalchemy import SQLAlchemy
24
+ The following example gives every model an integer primary key, or a foreign
25
+ key for joined-table inheritance.
24
26
25
- class ReprBase(Model):
26
- def __repr__(self):
27
- return "<{0} id: {1}>".format(self.__class__.__name__, self.id)
27
+ .. note ::
28
28
29
- db = SQLAlchemy(model_class=ReprBase)
29
+ Integer primary keys for everything is not necessarily the best database
30
+ design (that's up to your project's requirements), this is only an example.
30
31
31
- class MyModel(db.Model):
32
- ...
32
+ ::
33
33
34
- .. note ::
34
+ from flask_sqlalchemy import Model, SQLAlchemy
35
+ import sqlalchemy as sa
36
+ from sqlalchemy.ext.declarative import declared_attr, has_inherited_table
35
37
36
- While not strictly necessary to inherit from :class: `flask_sqlalchemy.Model `
37
- it is encouraged as future changes may cause incompatibility.
38
+ class IdModel(Model):
39
+ @declared_attr
40
+ def id(cls):
41
+ for base in cls.__mro__[1:-1]:
42
+ if getattr(base, '__table__', None) is not None:
43
+ type = sa.ForeignKey(base.id)
44
+ break
45
+ else:
46
+ type = sa.Integer
38
47
39
- .. note ::
48
+ return sa.Column(type, primary_key=True)
40
49
41
- If behavior is needed in only some models, not all, a better strategy
42
- is to use a Mixin, as exampled below.
50
+ db = SQLAlchemy(model_class=IdModel)
43
51
44
- While this particular example is more useful for debugging, it is possible to
45
- provide many augmentations to models that would otherwise be achieved with
46
- mixins instead. The above example is equivalent to the following::
52
+ class User(db.Model):
53
+ name = db.Column(db.String)
47
54
48
- class ReprBase(object):
49
- def __repr__(self):
50
- return "<{0} id: {1}>".format(self.__class__.__name__, self.id)
55
+ class Employee(User):
56
+ title = db.Column(db.String)
51
57
52
- db = SQLAlchemy()
53
58
54
- class MyModel(db. Model, ReprBase):
55
- ...
59
+ Model Mixins
60
+ ------------
56
61
57
- It also possible to provide default columns and properties to all models as well::
62
+ If behavior is only needed on some models rather than all models, use mixin
63
+ classes to customize only those models. For example, if some models should
64
+ track when they are created or updated::
58
65
59
- from flask_sqlalchemy import Model, SQLAlchemy
60
- from sqlalchemy import Column, DateTime
61
66
from datetime import datetime
62
67
63
- class TimestampedModel(Model):
64
- created_at = Column(DateTime, default=datetime.utcnow)
68
+ class TimestampMixin(object):
69
+ created = db.Column(
70
+ db.DateTime, nullable=False, default=datetime.utcnow)
71
+ updated = db.Column(db.DateTime, onupdate=datetime.utcnow)
65
72
66
- db = SQLAlchemy(model_class=TimestampedModel)
73
+ class Author(db.Model):
74
+ ...
67
75
68
- class MyModel( db.Model):
76
+ class Post(TimestampMixin, db.Model):
69
77
...
70
78
71
- All model classes extending from ``db.Model `` will now inherit a
72
- ``created_at `` column.
73
79
74
80
Query Class
75
81
-----------
76
82
77
- It is also possible to customize what is availble for use on the
83
+ It is also possible to customize what is available for use on the
78
84
special ``query `` property of models. For example, providing a
79
85
``get_or `` method::
80
86
@@ -86,19 +92,15 @@ special ``query`` property of models. For example, providing a
86
92
87
93
db = SQLAlchemy(query_class=GetOrQuery)
88
94
95
+ # get a user by id, or return an anonymous user instance
96
+ user = User.query.get_or(user_id, anonymous_user)
97
+
89
98
And now all queries executed from the special ``query `` property
90
99
on Flask-SQLAlchemy models can use the ``get_or `` method as part
91
100
of their queries. All relationships defined with
92
- ``db.relationship `` (but not :func: `sqlalchemy.relationship `)
101
+ ``db.relationship `` (but not :func: `sqlalchemy.orm. relationship `)
93
102
will also be provided with this functionality.
94
103
95
- .. warning ::
96
-
97
- Unlike a custom ``Model `` base class, it is required
98
- to either inherit from either :class: `flask_sqlalchemy.BaseQuery `
99
- or :func: `sqlalchemy.orm.Query ` in order to define a custom
100
- query class.
101
-
102
104
It also possible to define a custom query class for individual
103
105
relationships as well, by providing the ``query_class `` keyword
104
106
in the definition. This works with both ``db.relationship ``
@@ -109,9 +111,8 @@ and ``sqlalchemy.relationship``::
109
111
110
112
.. note ::
111
113
112
- If a query class is defined on a relationship, it will take
113
- precedence over the query class attached to its corresponding
114
- model.
114
+ If a query class is defined on a relationship, it will take precedence over
115
+ the query class attached to its corresponding model.
115
116
116
117
It is also possible to define a specific query class for individual models
117
118
by overriding the ``query_class `` class attribute on the model::
@@ -121,3 +122,69 @@ by overriding the ``query_class`` class attribute on the model::
121
122
122
123
In this case, the ``get_or `` method will be only availble on queries
123
124
orginating from ``MyModel.query ``.
125
+
126
+
127
+ Model Metaclass
128
+ ---------------
129
+
130
+ .. warning ::
131
+
132
+ Metaclasses are an advanced topic, and you probably don't need to customize
133
+ them to achieve what you want. It is mainly documented here to show how to
134
+ disable table name generation.
135
+
136
+ The model metaclass is responsible for setting up the SQLAlchemy internals when
137
+ defining model subclasses. Flask-SQLAlchemy adds some extra behaviors through
138
+ mixins; its default metaclass, :class: `~model.DefaultMeta `, inherits them all.
139
+
140
+ * :class: `~model.BindMetaMixin `: ``__bind_key__ `` is extracted from the class
141
+ and applied to the table. See :ref: `binds `.
142
+ * :class: `~model.NameMetaMixin `: If the model does not specify a
143
+ ``__tablename__ `` but does specify a primary key, a name is automatically
144
+ generated.
145
+
146
+ You can add your own behaviors by defining your own metaclass and creating the
147
+ declarative base yourself. Be sure to still inherit from the mixins you want
148
+ (or just inherit from the default metaclass).
149
+
150
+ Passing a declarative base class instead of a simple model base class, as shown
151
+ above, to ``base_class `` will cause Flask-SQLAlchemy to use this base instead
152
+ of constructing one with the default metaclass. ::
153
+
154
+ from flask_sqlalchemy import SQLAlchemy
155
+ from flask_sqlalchemy.model import DefaultMeta, Model
156
+
157
+ class CustomMeta(DefaultMeta):
158
+ def __init__(cls, name, bases, d):
159
+ # custom class setup could go here
160
+
161
+ # be sure to call super
162
+ super(CustomMeta, cls).__init__(name, bases, d)
163
+
164
+ # custom class-only methods could go here
165
+
166
+ db = SQLAlchemy(model_class=declarative_base(
167
+ cls=Model, metaclass=CustomMeta, name='Model'))
168
+
169
+ You can also pass whatever other arguments you want to
170
+ :func: `~sqlalchemy.ext.declarative.declarative_base ` to customize the base
171
+ class as needed.
172
+
173
+ Disabling Table Name Generation
174
+ ```````````````````````````````
175
+
176
+ Some projects prefer to set each model's ``__tablename__ `` manually rather than
177
+ relying on Flask-SQLAlchemy's detection and generation. The table name
178
+ generation can be disabled by defining a custom metaclass. ::
179
+
180
+ from flask_sqlalchemy.model import BindMetaMixin, Model
181
+ from sqlalchemy.ext.declarative import DeclarativeMeta, declarative_base
182
+
183
+ class NoNameMeta(BindMetaMixin, DeclarativeMeta):
184
+ pass
185
+
186
+ db = SQLAlchemy(model_class=declarative_base(
187
+ cls=Model, metaclass=NoNameMeta, name='Model'))
188
+
189
+ This creates a base that still supports the ``__bind_key__ `` feature but does
190
+ not generate table names.
0 commit comments