Project

General

Profile

Model » History » Version 28

Jan Klopper, 2017-09-19 09:43

1 1 Elmer de Looff
The µWeb framework provides a @model@ module with the intention of simplifying database access. The design goal is to provide a rich abstraction that
2 1 Elmer de Looff
* takes away the tedious work of retrieving, creating and deleting records
3 1 Elmer de Looff
* can load its parent objects automatically if so required
4 1 Elmer de Looff
* _does *not* get in the way of the developer_
5 1 Elmer de Looff
6 26 Arjen Pander
Making database interaction easier without restricting the abilities of the developer is our main goal. Some default mechanisms make assumptions on the way the database is organized, but these are well-documented, and it's entirely possible to change the behavior of these mechanisms.
7 1 Elmer de Looff
8 15 Elmer de Looff
{{toc}}
9 15 Elmer de Looff
10 15 Elmer de Looff
h1. Record
11 1 Elmer de Looff
12 2 Elmer de Looff
The basic idea of the @Record@ class is that it is a container for your database records, with related records automatically loaded as needed, and custom methods that provide more info, child objects, etc. Outlined below are the default features available, with minimal configuration requirements.
13 1 Elmer de Looff
14 22 Elmer de Looff
h2. Your first @Record@ class
15 1 Elmer de Looff
16 21 Elmer de Looff
To create your own @Record@ subclass, nothing is required beyond the class' name. The following example substitutes a complete working example:
17 1 Elmer de Looff
<pre><code class="python">
18 1 Elmer de Looff
from uweb import model
19 13 Jan Klopper
class Message(model.Record):
20 1 Elmer de Looff
  """Abstraction class for messages stored in the database."""
21 2 Elmer de Looff
</code></pre>
22 1 Elmer de Looff
23 22 Elmer de Looff
h2. Loading fields from primary key
24 1 Elmer de Looff
25 22 Elmer de Looff
The Record class comes loaded with a way to load records from your database using the @FromPrimary@ method. This is a classmethod available on the @Record@ class and all your own subclasses, and when given a connection and primary key value, will load that record from the database. Provided you have a database that looks like this:
26 1 Elmer de Looff
27 22 Elmer de Looff
<pre><code class="html">
28 22 Elmer de Looff
-- TABLE `message`
29 22 Elmer de Looff
+----+--------+--------------------------------------------------+
30 22 Elmer de Looff
| ID | author | message                                          |
31 22 Elmer de Looff
+----+--------+--------------------------------------------------+
32 22 Elmer de Looff
|  1 | Elmer  | First message!                                   |
33 22 Elmer de Looff
|  2 | Bobby  | Robert'); DROP TABLE Students;--                 |
34 22 Elmer de Looff
|  3 | Elmer  | You didn't think it would be this easy, did you? |
35 22 Elmer de Looff
+----+--------+--------------------------------------------------+
36 22 Elmer de Looff
</code></pre>
37 1 Elmer de Looff
38 22 Elmer de Looff
You can load data from this table with the following code:
39 22 Elmer de Looff
40 1 Elmer de Looff
<pre><code class="python">
41 22 Elmer de Looff
# The model:
42 1 Elmer de Looff
from uweb import model
43 22 Elmer de Looff
class Message(model.Record):
44 22 Elmer de Looff
  """Abstraction class for messages stored in the database."""
45 22 Elmer de Looff
46 22 Elmer de Looff
# Using this:
47 22 Elmer de Looff
>>> message = Message.FromPrimary(db_conn, 1)
48 22 Elmer de Looff
>>> print message
49 22 Elmer de Looff
Message({'message': u'First message!', 'ID': 1L, 'author': u'Elmer'})
50 22 Elmer de Looff
</code></pre>
51 22 Elmer de Looff
52 22 Elmer de Looff
h3. Changing the primary key field
53 22 Elmer de Looff
54 22 Elmer de Looff
By default, @Record@ uses a primary key called @'ID'@. You can change this to any value you like, and @FromPrimary@ will automatically work based on that value, and all other methods and functionality of the class will also use this new definition (deleting, creating and auto-loading from related tables, which are all explained later).
55 22 Elmer de Looff
56 22 Elmer de Looff
To change the primary key field, create a class with a defined @_PRIMARY_KEY@ class variable:
57 22 Elmer de Looff
<pre><code class="python">
58 22 Elmer de Looff
from uweb import model
59 1 Elmer de Looff
class Country(model.Record):
60 6 Elmer de Looff
  """Abstraction class for a country table.
61 1 Elmer de Looff
62 1 Elmer de Looff
  This class uses the ISO-3166-1 alpha2 country code as primary key.
63 1 Elmer de Looff
  """
64 1 Elmer de Looff
  _PRIMARY_KEY = 'alpha2'
65 1 Elmer de Looff
</code></pre>
66 1 Elmer de Looff
67 22 Elmer de Looff
h3. Compound primary keys
68 1 Elmer de Looff
69 22 Elmer de Looff
The µWeb model also supports compound primary keys, with one limitation: @AUTO_INCREMENT@ fields are not supported for creation of the Record, all values need to be provided for it.
70 1 Elmer de Looff
71 24 Jan Klopper
Loading values from a compound primary key works by passing a tuple instead of a single value:
72 22 Elmer de Looff
73 1 Elmer de Looff
<pre><code class="python">
74 22 Elmer de Looff
# The model:
75 1 Elmer de Looff
from uweb import model
76 22 Elmer de Looff
class MonthReport(model.Record):
77 22 Elmer de Looff
  """Abstraction class for the monthReport table.
78 22 Elmer de Looff
79 22 Elmer de Looff
  This is keyed on a composite of both year and month, foregoing the need for a separate AUTO_INCREMENT field.
80 22 Elmer de Looff
  """
81 22 Elmer de Looff
  _PRIMARY_KEY = 'year', 'month'
82 22 Elmer de Looff
83 22 Elmer de Looff
# Using this:
84 22 Elmer de Looff
>>> report = MonthReport.FromPrimary(db_conn, (2012, 5))
85 22 Elmer de Looff
>>> print report
86 22 Elmer de Looff
Message({'report': 'Things went really well', 'month': 5, 'year': 2012})
87 1 Elmer de Looff
</code></pre>
88 1 Elmer de Looff
89 22 Elmer de Looff
h3. Class and table relation
90 1 Elmer de Looff
91 22 Elmer de Looff
By default, the assumption is made that the table name is the same as the class name, with the first letter lowercase. *The table related to the class @Message@ would be @message@.* To change this behavior, assign your own table name to the @_TABLE@ class constant. This new table name will then be used in all built-in Record methods:
92 1 Elmer de Looff
93 1 Elmer de Looff
<pre><code class="python">
94 1 Elmer de Looff
from uweb import model
95 1 Elmer de Looff
class Message(model.Record):
96 1 Elmer de Looff
  """Abstraction class for messages stored in the database."""
97 22 Elmer de Looff
  _TABLE = 'MyMessage'
98 22 Elmer de Looff
</code></pre>
99 1 Elmer de Looff
100 22 Elmer de Looff
Alternatively, you can override the @TableName@ class-method to alter the table-name transformation that is done.
101 22 Elmer de Looff
102 22 Elmer de Looff
h2. Creating records
103 22 Elmer de Looff
104 22 Elmer de Looff
To create a record in the database, you can use the classmethod @Create@. This takes the connection and a dictionary of the keys and values that should be inserted into the database. Using the @Message@ class we defined earlier, creating a new record is a relatively simple call:
105 22 Elmer de Looff
106 22 Elmer de Looff
<pre><code class="python">
107 22 Elmer de Looff
>>> message = Message.Create(db_conn, {'author': 'Bob', 'message': 'Another message'})
108 1 Elmer de Looff
>>> print message
109 22 Elmer de Looff
Message({'message': 'Another message', 'ID': 4L, 'author': 'Bob'})
110 1 Elmer de Looff
</code></pre>
111 1 Elmer de Looff
112 23 Jan Klopper
*N.B.* Skipping fields that are optional in the database is allowed, but their default values assigned by the database will _not_ be reflected in the object. That is, the record will not be reloaded after storing.
113 1 Elmer de Looff
114 22 Elmer de Looff
h2. Deleting records
115 6 Elmer de Looff
116 22 Elmer de Looff
Records can be deleted from the database either from a loaded object, or using the @DeletePrimary@ classmethod. This latter removes the record from the database using the primary key to select it.
117 6 Elmer de Looff
118 22 Elmer de Looff
<pre><code class="python">
119 22 Elmer de Looff
class Message(model.Record):
120 22 Elmer de Looff
  """Abstraction class for messages records."""
121 16 Elmer de Looff
122 22 Elmer de Looff
# Loading and deleting an active record.
123 22 Elmer de Looff
>>> bad_record = Message.FromPrimary(db_connection, 3)
124 22 Elmer de Looff
>>> bad_record.Delete()
125 22 Elmer de Looff
126 22 Elmer de Looff
# Deleting a record based on its primary key.
127 22 Elmer de Looff
>>> Message.DeletePrimary(db_connection, 2)
128 22 Elmer de Looff
</code></pre>
129 22 Elmer de Looff
130 22 Elmer de Looff
h2. Listing all records
131 22 Elmer de Looff
132 22 Elmer de Looff
For situations where all records must be retrieved or processed, there is the @List@ classmethod. This takes the connection as argument and iterates over all records in the database:
133 22 Elmer de Looff
134 12 Elmer de Looff
<pre><code class="python">
135 7 Elmer de Looff
class Message(model.Record):
136 22 Elmer de Looff
  """Abstraction class for messages records."""
137 7 Elmer de Looff
138 22 Elmer de Looff
# List all messages:
139 22 Elmer de Looff
>>> for message in Message.List(db_connection):
140 22 Elmer de Looff
...   print message
141 22 Elmer de Looff
... 
142 22 Elmer de Looff
Message({'message': u'First message!', 'ID': 1L, 'author': 1})
143 22 Elmer de Looff
Message({'message': u"Robert'); DROP TABLE Students;--", 'ID': 2L, 'author': 2})
144 22 Elmer de Looff
Message({'message': u"You didn't think it would be this easy, did you?", 'ID': 3L, 'author': 1})
145 7 Elmer de Looff
</code></pre>
146 7 Elmer de Looff
147 25 Jan Klopper
h3. Filtering and sorting using the list method:
148 25 Jan Klopper
149 25 Jan Klopper
Possible arguments and default values:
150 25 Jan Klopper
151 25 Jan Klopper
<pre>
152 25 Jan Klopper
      @ connection: object
153 25 Jan Klopper
        Database connection to use.
154 25 Jan Klopper
      % conditions: str / iterable ~~ None
155 25 Jan Klopper
        Optional query portion that will be used to limit the list of results.
156 25 Jan Klopper
        If multiple conditions are provided, they are joined on an 'AND' string.
157 25 Jan Klopper
      % limit: int ~~ None
158 25 Jan Klopper
        Specifies a maximum number of items to be yielded. The limit happens on
159 25 Jan Klopper
        the database side, limiting the query results.
160 25 Jan Klopper
      % offset: int ~~ None
161 25 Jan Klopper
        Specifies the offset at which the yielded items should start. Combined
162 25 Jan Klopper
        with limit this enables proper pagination.
163 25 Jan Klopper
      % order: iterable of str/2-tuple
164 25 Jan Klopper
        Defines the fields on which the output should be ordered. This should
165 25 Jan Klopper
        be a list of strings or 2-tuples. The string or first item indicates the
166 25 Jan Klopper
        field, the second argument defines descending order (desc. if True).
167 25 Jan Klopper
      % yield_unlimited_total_first: bool ~~ False
168 25 Jan Klopper
        Instead of yielding only Record objects, the first item returned is the
169 25 Jan Klopper
        number of results from the query if it had been executed without limit.
170 25 Jan Klopper
</pre>
171 25 Jan Klopper
172 25 Jan Klopper
h3. Examples:
173 25 Jan Klopper
174 25 Jan Klopper
<pre><code class="python">
175 25 Jan Klopper
# List all messages:
176 25 Jan Klopper
Message.List(db_connection, conditions={'author':1}, limit=15)
177 25 Jan Klopper
# list only the first 15 records where the conditions are met.
178 25 Jan Klopper
179 25 Jan Klopper
# List all messages:
180 25 Jan Klopper
Message.List(db_connection, limit=20, offset=10)
181 25 Jan Klopper
# lists records 10 to 30
182 28 Jan Klopper
183 28 Jan Klopper
# List all messages:
184 28 Jan Klopper
Message.List(db_connection, limit=20, offset=10, order=[('ID', True)])
185 28 Jan Klopper
# lists records 10 to 30 after the table is sorted in reverse by ID.
186 25 Jan Klopper
</code></pre>
187 25 Jan Klopper
188 7 Elmer de Looff
h2. On-demand loading of referenced records.
189 7 Elmer de Looff
190 22 Elmer de Looff
In databases that are more complex than a single table (nearly ''all''), information is often normalized. That is, the author information in our previously demonstrated *message* table will be stored in a separate *author* table. The author field on message records will be a _reference_ to a record in the author table.
191 7 Elmer de Looff
192 7 Elmer de Looff
Consider the following tables in your database:
193 7 Elmer de Looff
<pre><code class="html">
194 1 Elmer de Looff
-- TABLE `message`
195 7 Elmer de Looff
+----+--------+--------------------------------------------------+
196 7 Elmer de Looff
| ID | author | message                                          |
197 1 Elmer de Looff
+----+--------+--------------------------------------------------+
198 7 Elmer de Looff
|  1 |      1 | First message!                                   |
199 7 Elmer de Looff
|  2 |      2 | Robert'); DROP TABLE Students;--                 |
200 7 Elmer de Looff
|  3 |      1 | You didn't think it would be this easy, did you? |
201 7 Elmer de Looff
+----+--------+--------------------------------------------------+
202 13 Jan Klopper
203 7 Elmer de Looff
-- TABLE `author`
204 7 Elmer de Looff
+----+-------+--------------------+
205 1 Elmer de Looff
| ID | name  | emailAddress       |
206 7 Elmer de Looff
+----+-------+--------------------+
207 1 Elmer de Looff
|  1 | Elmer | elmer@underdark.nl |
208 7 Elmer de Looff
|  2 | Bobby | bobby@tables.com   |
209 1 Elmer de Looff
+----+-------+--------------------+
210 1 Elmer de Looff
</code></pre>
211 7 Elmer de Looff
212 1 Elmer de Looff
And the following class definitions in Python:
213 1 Elmer de Looff
214 1 Elmer de Looff
<pre><code class="python">
215 1 Elmer de Looff
from uweb import model
216 1 Elmer de Looff
class Author(model.Record):
217 7 Elmer de Looff
  """Abstraction class for author records."""
218 7 Elmer de Looff
219 16 Elmer de Looff
class Message(model.Record):
220 7 Elmer de Looff
  """Abstraction class for messages records."""
221 8 Elmer de Looff
</code></pre>
222 1 Elmer de Looff
223 8 Elmer de Looff
This makes it possible to retrieve a message, and from that Message object, retrieve the author information. This is done when the information is requested, and not pre-loaded beforehand. This means that retrieving a thousand Message objects will *not* trigger an additional 1000 queries to retrieve the author information, if that information might not be used at all.
224 8 Elmer de Looff
225 8 Elmer de Looff
<pre><code class="python">
226 22 Elmer de Looff
>>> message = Message.FromPrimary(db_conn, 1)
227 8 Elmer de Looff
>>> message
228 8 Elmer de Looff
Message({'message': u'First message!', 'ID': 1L, 'author': 1})
229 8 Elmer de Looff
# This is the same message we saw before, without author information.
230 1 Elmer de Looff
# However, retrieving the author field specifically, provides its record:
231 8 Elmer de Looff
>>> message['author']
232 1 Elmer de Looff
Author({'emailAddress': u'elmer@underdark.nl', 'ID': 1, 'name': u'Elmer'})
233 1 Elmer de Looff
>>> message
234 1 Elmer de Looff
Message({'message': u'First message!', 'ID': 1L,
235 1 Elmer de Looff
         'author': Author({'emailAddress': u'elmer@underdark.nl', 'ID': 1, 'name': u'Elmer'})})
236 1 Elmer de Looff
</code></pre>
237 1 Elmer de Looff
238 22 Elmer de Looff
This works on the assumption that *any field name that is also the table name of another Record class, is a reference to that table*. In the case of the example above: The message table contains a field _author_. There exists a Record subclass for that table (namely _Author_, table 'author'). The value of @message['author']@ (=@1@), is now used to load an Author record using the FromPrimary classmethod, with @1@ as the primary key value.
239 10 Elmer de Looff
240 10 Elmer de Looff
# @message['author']@ uses the _author_ field
241 22 Elmer de Looff
# _author_ table is represented by Author class
242 10 Elmer de Looff
# @message['author']@ is replaced by @Author.FromPrimary(db_connection, message['author']@
243 10 Elmer de Looff
244 22 Elmer de Looff
h3. Customize table-references
245 10 Elmer de Looff
246 22 Elmer de Looff
The auto-loading behavior can be modified using the @_FOREIGN_RELATIONS@ class constant. This provides a mapping that specifies (and overrides) which Record classes should be used to resolve references from fields. The key for the mapping is a field name (string), and the corresponding value can be a class or @None@.
247 10 Elmer de Looff
248 22 Elmer de Looff
* @None@ specifies that the field does *not* represent a reference, and should be used as-is.
249 22 Elmer de Looff
* Classes may be given as string because at the time of evaluation, not all classes exist, and attempting using a class directly might result in a @NameError@. This "class as string" exception only exists for classes that are defined in the same module, and exists so that the model does not force you to define your classes in a certain order. It also enables the case where two tables cross-reference eachother.
250 22 Elmer de Looff
251 22 Elmer de Looff
The following is an example case where the table names are plural, but the field names are singular:
252 22 Elmer de Looff
253 10 Elmer de Looff
<pre><code class="python">
254 10 Elmer de Looff
from uweb import model
255 10 Elmer de Looff
class Author(model.Record):
256 12 Elmer de Looff
  """Abstraction class for author records."""
257 10 Elmer de Looff
  _TABLE = 'authors'
258 10 Elmer de Looff
259 1 Elmer de Looff
class Message(model.Record):
260 10 Elmer de Looff
  """Abstraction class for messages records."""
261 10 Elmer de Looff
  _TABLE = 'messages'
262 10 Elmer de Looff
  _FOREIGN_RELATIONS = {'author': Author}
263 10 Elmer de Looff
</code></pre>
264 10 Elmer de Looff
265 10 Elmer de Looff
h2. Loading child objects (1-to-n relations)
266 10 Elmer de Looff
267 22 Elmer de Looff
The model provides a generic method to retrieve child records (that is, _1 to n_ relations) of a record. The desired relations _should_ have an associated Record class. The method to use is @_Children@, which is a private method of any @Record@ class. As its argument, it needs the name of a child class. Returned is an iterator that yields instances of the given @Record@ subclass. 
268 16 Elmer de Looff
269 10 Elmer de Looff
Given its name and usage, the suggested usage of this is to wrap a more descriptive method around this:
270 10 Elmer de Looff
271 16 Elmer de Looff
<pre><code class="python">
272 10 Elmer de Looff
from uweb import model
273 10 Elmer de Looff
class Author(model.Record):
274 10 Elmer de Looff
  """Abstraction class for author records."""
275 10 Elmer de Looff
  def Messages(self):
276 16 Elmer de Looff
    """Returns an iterator for all messages written by this author."""
277 10 Elmer de Looff
    return self._Children(Message)
278 10 Elmer de Looff
279 10 Elmer de Looff
class Message(model.Record):
280 10 Elmer de Looff
  """Abstraction class for messages records."""
281 10 Elmer de Looff
282 10 Elmer de Looff
# Caller code
283 12 Elmer de Looff
>>> elmer = Author.FromPrimary(db_connection, 1)
284 10 Elmer de Looff
>>> for message in elmer.Messages():
285 10 Elmer de Looff
...   print message
286 10 Elmer de Looff
Message({'message': u'First message!', 'ID': 1L,
287 10 Elmer de Looff
         'author': Author({'emailAddress': u'elmer@underdark.nl', 'ID': 1, 'name': u'Elmer'})})
288 10 Elmer de Looff
Message({'message': u"You didn't think it would be this easy, did you?", 'ID': 3L,
289 10 Elmer de Looff
         'author': Author({'emailAddress': u'elmer@underdark.nl', 'ID': 1, 'name': u'Elmer'})})
290 10 Elmer de Looff
# Reflowing to keep things legible
291 10 Elmer de Looff
</code></pre>
292 10 Elmer de Looff
293 10 Elmer de Looff
What you can see here is that all messages written by the given author are retrieved from the database, and presented. This is done with a single database query, where the _child_ Record's table is searched for rows where the @relation_field@ is equal to the parent Record's primary key value. This @relation_field@ is an optional argument to the @_Children@ method, and defaults to the class' table name.
294 10 Elmer de Looff
295 10 Elmer de Looff
*N.B. @print@ and the methods @(iter)items@, @(iter)values@ all cause the object's foreign relations to be retrieved.*
296 10 Elmer de Looff
297 22 Elmer de Looff
The same example, this time with pluralized table names:
298 16 Elmer de Looff
299 10 Elmer de Looff
<pre><code class="python">
300 10 Elmer de Looff
class Author(model.Record):
301 10 Elmer de Looff
  """Abstraction class for author records."""
302 10 Elmer de Looff
  _TABLE = 'authors'
303 10 Elmer de Looff
304 10 Elmer de Looff
  def Messages(self):
305 10 Elmer de Looff
    """Returns an iterator for all messages written by this author."""
306 10 Elmer de Looff
    return self._Children(Message, relation_field='author')
307 10 Elmer de Looff
308 10 Elmer de Looff
class Message(model.Record):
309 10 Elmer de Looff
  """Abstraction class for messages records."""
310 10 Elmer de Looff
  _TABLE = 'messages'
311 10 Elmer de Looff
  _FOREIGN_RELATIONS = {'author': Author}
312 10 Elmer de Looff
</code></pre>
313 16 Elmer de Looff
314 10 Elmer de Looff
h2. Updating a record
315 10 Elmer de Looff
316 10 Elmer de Looff
After loading a record, it can be altered, and saved. These changes (and optionally changes to nested records), will be committed to the database, and reflected in the current loaded record.
317 12 Elmer de Looff
318 10 Elmer de Looff
<pre><code class="python">
319 10 Elmer de Looff
class Author(model.Record):
320 10 Elmer de Looff
  """Abstraction class for author records."""
321 10 Elmer de Looff
322 10 Elmer de Looff
class Message(model.Record):
323 10 Elmer de Looff
  """Abstraction class for messages records."""
324 10 Elmer de Looff
325 10 Elmer de Looff
>>> retort = Message.FromPrimary(db_connection, 3)
326 16 Elmer de Looff
>>> retort['message'] = "Please go away Bobby."
327 10 Elmer de Looff
>>> # Our changes are not yet reflected in the database:
328 10 Elmer de Looff
>>> print Message.FromPrimary(db_connection, 3)
329 10 Elmer de Looff
Message({'message': u"You didn't think it would be this easy, did you?", 'ID': 3L,
330 10 Elmer de Looff
         'author': Author({'emailAddress': u'elmer@underdark.nl', 'ID': 1, 'name': u'Elmer'})})
331 10 Elmer de Looff
>>> retort.Save()
332 1 Elmer de Looff
>>> # Now our changes are committed to the database:
333 19 Elmer de Looff
>>> print Message.FromPrimary(db_connection, 3)
334 19 Elmer de Looff
Message({'message': u'Please go away Bobby.', 'ID': 3L,
335 20 Elmer de Looff
         'author': Author({'emailAddress': u'elmer@underdark.nl', 'ID': 1, 'name': u'Elmer'})})
336 1 Elmer de Looff
</code></pre>
337 1 Elmer de Looff
338 22 Elmer de Looff
To save all changes in related fields, we can provide the named argument *save_foreign* and set it to _True_. This way we could alter both the author name and the message itself in one database transaction.
339 12 Elmer de Looff
340 10 Elmer de Looff
h2. Comparisons
341 10 Elmer de Looff
342 10 Elmer de Looff
h3. Equality
343 20 Elmer de Looff
344 10 Elmer de Looff
Records must pass the following criteria to be considered equal to one another.:
345 18 Elmer de Looff
# *Type*: Two objects must be of the same type (class)
346 16 Elmer de Looff
# *Primary key*: The primary key values must compare equal
347 16 Elmer de Looff
# *Foreign relations*: Foreign relations must be the same. If these are not resolved in one object but are in the other, the primary key of the resolved object will be compared to the data of the other record.
348 16 Elmer de Looff
# *Data*: All remaining data fields must be equal and symmetric (i.e. both objects describe the same fields)
349 16 Elmer de Looff
350 1 Elmer de Looff
h3. Greater / smaller
351 1 Elmer de Looff
352 1 Elmer de Looff
Comparing two objects with one another to tell their relative order can _only_ be done if they are of the same type. If they are, the comparison is done based on the primary key values of the records. In most cases this will result in an ordering similar to the database-insert order.
353 1 Elmer de Looff
354 27 Jan Klopper
h3. Hooks
355 27 Jan Klopper
356 27 Jan Klopper
To facilitate various checks or custom code when records are created, initialized, inserted etc the following hooks are available:
357 27 Jan Klopper
358 27 Jan Klopper
* _PreCreate
359 27 Jan Klopper
* _PreSave
360 27 Jan Klopper
* _PostInit
361 27 Jan Klopper
* _PostCreate
362 27 Jan Klopper
* _PostSave
363 27 Jan Klopper
364 27 Jan Klopper
<pre><code class="python">
365 27 Jan Klopper
from uweb import model
366 27 Jan Klopper
import hashlib
367 27 Jan Klopper
368 27 Jan Klopper
class Author(model.Record):
369 27 Jan Klopper
  """Demonstrate a PreCreate method."""
370 27 Jan Klopper
  
371 27 Jan Klopper
  def _PreCreate(self, cursor):
372 27 Jan Klopper
    """Sets the API key for the new record and insert the dateCreated value"""
373 27 Jan Klopper
    now = datetime.datetime.utcnow()
374 27 Jan Klopper
    self['dateCreated'] = now
375 27 Jan Klopper
    self['apikey'] = hashlib.md5(str(random.randrange(9999999999))).hexdigest()
376 27 Jan Klopper
</code></pre>
377 27 Jan Klopper
378 1 Elmer de Looff
h1. VersionedRecord
379 27 Jan Klopper
380 27 Jan Klopper
The versionedRecord class provides a dual key table, in which there is one primary key (for example a record ID) which looks up the individual versioned records, and an overall key which is used to return the most recent version.
381 27 Jan Klopper
If for example you'd need a table that holds all of your customer details. And you also need to keep older versions of their details available for older invoices, you could used a VersionedRecord to hold this information.
382 27 Jan Klopper
You would use the overall key to load the current / newest customer details, and you would use the specific record ID to keep track of which version of the customer details you need for a specific invoice.
383 27 Jan Klopper
384 27 Jan Klopper
Te VersionedRecord acts much like the Record class.
385 27 Jan Klopper
It introduces:
386 27 Jan Klopper
387 27 Jan Klopper
The method: 'Versions' which lists all the versions for a specific identifier.
388 27 Jan Klopper
The property: 'identifier' which returns the version value for the current record.
389 1 Elmer de Looff
390 1 Elmer de Looff
h1. MongoRecord