Project

Profile

Help

30-Development » History » Sprint/Milestone 1

amacdona@redhat.com, 01/25/2018 08:47 PM

1 1 amacdona@redhat.com
# 30-Development
2
3
.. warning::  
4
All documents within the 3.0-Development section should be considered temporary, and will be  
5
removed or relocated prior to the release of Pulp 3.0.
6
7
3.0 Development  
8
\===============
9
10
The goal of this section is to create a place for living documents related to the development of  
11
Pulp 3.0. Some parts may grow and replace current documentation (plugin API) and others may be  
12
temporary guides to making changes (translating from Mongo to Postgres).
13
14
.. toctree::  
15
:maxdepth: 3
16
17
~~~
18
app-layout
19
data-modeling
20
db-translation-guide
21
rest-api
22
~~~
23
24
Pulp 3 and Python 3  
25
\-------------------
26
27
Pulp 3 will only support Python 3.5+. When introducing new dependencies to Pulp,  
28
ensure they support Python 3 (see http://fedora.portingdb.xyz/). If they do not,  
29
please file an issue in Redmine (related to https://pulp.plan.io/issues/2247) to  
30
track the conversion of the dependency to support Python 3 and begin working with  
31
upstream to convert the package.
32
33
Docstrings  
34
\----------
35
36
\`PUP-2 \<https://github.com/pulp/pups/blob/master/pup-0002.md&gt;\`_ adopted Google style for  
37
:ref:\`google-docstrings\`. When porting code from Pulp 2 to Pulp 3, convert all the docstrings to the  
38
new style. vim-style regexes can be used to speed up the process. Together, these will convert all  
39
of the parameters to Google Style::
40
41
1.  Typed params  
42
    %s/\\(\\s\*\\):param\\s\\+\\(.\*\\):\\s\\+\\(.\*\\)\\n\\s\*:type\\s\\+.\\+:\\s\\+\\(.\*\\)/\\1 \\2 (\\4): \\3
43
44
<!-- end list -->
45
46
1.  Untyped params  
47
    %s/\\(\\s\*\\):param\\s\\+\\(.\*\\):\\s\\+\\(.\*\\)/\\1 \\2: \\3
48
49
Data Modeling  
50
\=============
51
52
Introduction  
53
\^<sup>\^\^\^\^\^\^\^\^</sup>\^
54
55
The Pulp 3 data modeling effort is not just a translation or porting effort but instead  
56
an effort to build Pulp 3 using Pulp 2. Remodeling will include changes to leverage the  
57
relational capabilities of postgres but also to improve upon the Pulp 2 model.
58
59
When creating each Pulp 3 model object, consider the following:
60
61
\- Understand what the Pulp 2 model (collection) stores and how the information  
62
is used by pulp.
63
64
\- Name the model/table appropriately. A table is implicitly plural. Naming a  
65
table \`repository\` is more appropriate than \`repositories\`.
66
67
\- Name the model class appropriately. Each model object is a row in the table  
68
an not a collection. Class names should be singular. For example, \`Repository\`  
69
is a more appropriate class name than \`Repositories\`.
70
71
\- Do not use multiple inheritance (mixins) unless there is no other choice. Although  
72
they are convenient, they greatly diminish the clarity of the model and as with  
73
multiple inheritance in general, can result in untended behavior. With a little extra  
74
effort, a proper class hierarchy is almost always possible and will be better in the end.
75
76
\- Question the existence, type, and naming of all fields. Field names beginning with \`\_\`  
77
should not exist in Pulp 3. The primary key field name is \`id\` and already defined in the  
78
\`Model\` base class. All \`display_name\` fields need to be renamed to \`name\`. Also, avoid  
79
redundant scope by prefixing field names (or any attribute) with the name of the class.  
80
For example: \`Task.task_type\`.
81
82
\- Question all methods (including @property). We **only** want those methods that encapsulate  
83
significant complexity and are widely used. Most of the methods on Pulp 2 models will likely  
84
not be necessary or wanted in the Pulp 3 models. Let's keep the model interface as clean  
85
as possible.
86
87
\- Most string fields will be \`TextField\`. When the field is required and indexed,  
88
use: \`TextField(db_index=True)\`. When required but **not** indexed,  
89
use: \`TextField(blank=False, default=None)\`. This ensures integrity at the model and  
90
database layer(s) and supports validation at the REST layer.
91
92
\- All fields containing **ISO-8601** strings must be converted to \`DateTimeField\`.
93
94
\- Think about **natural** keys. For example, each repository has a unique name (instead of repo_id  
95
like in Pulp 2) that is known to users and is not the primary key (\`id\` is). The \`name\` is  
96
the **natural** key. Don't forget the index. See: https://en.wikipedia.org/wiki/Natural_key
97
98
\- Define a \`natural_key()\` method in each model. This both documents the **natural** key and  
99
will be used by the django serializer.  
100
See: https://docs.djangoproject.com/en/1.8/topics/serialization/#serialization-of-natural-keys
101
102
Development Process  
103
\^<sup>\^\^\^\^\^\^\^\^\^\^\^\^\^\^\^</sup>\^
104
105
\- The **refactor** tasks (in Redmine) group related collections.
106
107
\- The Pulp 3 models are grouped into modules within the \`models\` package.
108
109
\- Foreach model class defined, A **refactor** task needs to be created in Redmine for  
110
migrating the data. See existing examples.
111
112
\- Foreach model class defined, add a section to the **Notes** section below.
113
114
\- Add unit tests for models. Only methods encapsulating complexity need tests.  
115
In addition to this, add a set of **howto** tests to demonstrate common use cases for the  
116
model. They are intended to provide an example to developers and sanity testing only.  
117
100% coverage is not expected. Also, we don't need to test django itself.
118
119
Notes About Models  
120
\^<sup>\^\^\^\^\^\^\^\^\^\^\^\^\^\^</sup>\^
121
122
.. note:: This article is a stub. You can help by expanding it.
123
124
This section captures notes about each model. Developers should explain differences  
125
between the Pulp 2 and Pulp 3. This is a good place to give examples of how models are intended  
126
to be used or extended by plugins. Except for how to extend a class, refer to the **howto** unit  
127
tests instead of including code blocks here.
128
129
Consumer  
130
\^<sup>\^\^\^\^</sup>\^
131
132
The consumer models are much like that in Pulp 2 except the fields related to the removed  
133
**nodes** and **agent** functionality are gone. The \`bind\` has be replaced with a simple relation  
134
that is managed by django because no addition fields on the join table are needed.
135
136
The **applicability** models/tables have not been included because **applicability** is not a generic  
137
platform concept. Errata and RPM applicability is owned by the RPM plugin. That said,  
138
the \`ConsumerContent\` model provides a base class for plugins to model content that is installed  
139
on a consumer.
140
141
Repository  
142
\^<sup>\^\^\^\^\^\^</sup>\^
143
144
First, \`Distributor\` has been renamed to \`Publisher\` because it seems more appropriate.
145
146
The repository models include importers and publishers. The main difference being the  
147
consolidation of the importer and publisher and its configuration. In the model, a  
148
\`ContentAdaptor\` is the base for plugin contributed models that can be associated to a repository.  
149
On importer and publisher base models, the **standard** configuration settings that were  
150
separate documents in Pulp 2 are attributes of the importer and publishers itself. These models  
151
follow the **master-detail** pattern. Adaptors needing additional configuration, need to extend the  
152
base model (master) and add the extra fields on a new (detail) model.
153
154
The concept of a repository group distributor has been discarded. This concept and associated flows  
155
were flawed in Pulp 2.
156
157
Examples:
158
159
.. code-block:: python
160
161
~~~
162
Class MyImporter(Importer):
163
~~~
164
165
~~~
166
field_1 = models.TextField()
167
field_2 = models.TextField()
168
~~~
169
170
Database Field Translation Guide  
171
\================================
172
173
Introduction  
174
\------------
175
176
Converting from mongo to postgres should be recognized from the outset as a monumental task.  
177
In choosing Django as our ORM, this task has hopefully been made a little bit easier by bringing in  
178
such a mature and well-supported framework. Great care has been taken to make full use of Django's  
179
offerings when coming up with techniques and guidelines for converting our non-relational mongo  
180
database over to postgres.
181
182
Django  
183
\------
184
185
Django has many layers, including the data model layer, the view layer, etc. This document focuses  
186
on the model layer, and uses Django's terminology where applicable. Furthermore, every effort should  
187
be made to adhere to existing Django functionality so that the full benefits of adopting this  
188
framework can be realized.
189
190
https://docs.djangoproject.com/en/1.8/topics/db/models/
191
192
Tutorial  
193
\^<sup>\^\^\^\^</sup>\^
194
195
If you aren't already familiar with some of the features that Django provides, part 1 of the Django  
196
tutorial provides an excellent introduction. It covers things like starting a project for the first  
197
time, starting the development web server, and accessing models. The tutorial pages that come after  
198
part 1 are largely irrelevant for Pulp 3, but are a good exercise nonetheless for someone looking to  
199
get to know Django a little bit better.
200
201
https://docs.djangoproject.com/en/1.8/intro/tutorial01/
202
203
Django Concepts  
204
\---------------
205
206
There are specific Django concepts that deserve special attention before getting into some of the  
207
finer details of how the relation data model should be structured, which are outlined in subsections  
208
below.
209
210
Model Inheritance  
211
\^<sup>\^\^\^\^\^\^\^\^\^\^\^\^\^</sup>\^
212
213
https://docs.djangoproject.com/en/1.8/topics/db/models/#model-inheritance
214
215
Django provides three different mechanisms for supporting object-oriented inheritance in its  
216
Model classes. Each method provides specific benefits to Pulp that are outlined here.
217
218
Additionally, each of these model inheritance mechanisms can be combined with the other mechanisms  
219
as-needed to create a sound object-oriented design that also generates a reasonable database schema.
220
221
Abstract Base classes  
222
\*****\*****\*\*\*\*\*\*\*\*\*\*\*\*\*
223
224
https://docs.djangoproject.com/en/1.8/topics/db/models/#abstract-base-classes
225
226
Many of our ContentUnit classes have common fields and/or behavior. Abstract base classes make it  
227
trivial for us to put those common bits of code in a single place, to be inherited by Model classes in  
228
completely standard and pythonic ways, such as with subclassing or mixins.
229
230
The many different RPM-like ContentUnit subclasses use this extensively, combining in the  
231
"detail" classes to make the complete unit model for a given type.
232
233
Multi-table Inheritance  
234
\*****\*****\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*
235
236
https://docs.djangoproject.com/en/1.8/topics/db/models/#multi-table-inheritance
237
238
This is probably the most important Model inheritance mechanism, as it is used to implement the  
239
ContentUnit "master-detail" relationship, where the "master" content units contain all fields  
240
common to all (or most) ContentUnits, and the "detail" content units contain all of the type-specific  
241
fields for that unit type.
242
243
On the database level, the information representing a ContentUnit resides in at least two database  
244
tables (the master ContentUnit table and the detail table), and is seamlessly joined by Django on  
245
instances of the detail ContentUnit model (e.g. RPM, a puppet Module, etc).
246
247
More information on this is documented later in the section describing ContentUnit changes.
248
249
.. note::  
250
If you go by what wikipedia has to say about master-detail relationships, this isn't quite that.  
251
However, it makes it easy to refer to both sides of the master/detail relationship with terms that  
252
are easy to understand in the context of ContentUnits, so it's worth appropriating those  
253
terms for Pulp's purposes here.
254
255
~~~
256
https://en.wikipedia.org/wiki/Master%E2%80%93detail_interface#Data_model
257
~~~
258
259
Proxy Models  
260
\*****\*****\*\*\*\*
261
262
https://docs.djangoproject.com/en/1.8/topics/db/models/#proxy-models  
263
https://docs.djangoproject.com/en/1.8/topics/db/queries/#backwards-related-objects
264
265
Not currently used in Pulp, but mentioned here for docs completeness.
266
267
Generic Relations  
268
\^<sup>\^\^\^\^\^\^\^\^\^\^\^\^\^</sup>\^
269
270
https://docs.djangoproject.com/en/1.8/ref/contrib/contenttypes/#generic-relations
271
272
Django's Generic Relations give us the ability to associate many models to one model in a more  
273
flexible was than a normal ForeignKey. Normally used for things like object tagging,  
274
the Generic Relations that come with Django's contenttypes framework can be used by Pulp to easily  
275
associate a generic Django Model with any number of other models that can benefit from storing the  
276
information captured by the generic model.
277
278
A good example of this are the various Generic Key/Value stores that can be associated with any other  
279
Model, "Notes" and "Config".
280
281
MongoEngine to Django Field Conversions  
282
\---------------------------------------
283
284
These are the MongoEngine field types currently used by Pulp, and guidelines on converting them to  
285
Postgres. Since MongoEngine started out to get mongodb working as a Django backend, most fields have  
286
direct counterparts in Django. The following subsections are the MongoEngine fields currently used in  
287
Pulp, with applicable postgres datatypes and Django field alternatives listed inside.
288
289
In each MongoEngine field section there will be a link to that MongoEngine field's documentation,  
290
brief information about the corresponding postgres data type, subsections detailing the Django  
291
field or fields that should be used when translating a given MongoEngine field, and a list of  
292
files in which that MongoEngine field is being used.
293
294
StringField  
295
\^<sup>\^\^\^\^\^\^\^</sup>\^
296
297
http://docs.mongoengine.org/apireference.html#mongoengine.fields.StringField
298
299
This field can be represented by one of two Django fields, depending on which Postgres column is a  
300
better fit for the data being stored in it.
301
302
Postgres datatype reference:  
303
https://www.postgresql.org/docs/current/static/datatype-character.html
304
305
For our purposes, only varchar and text are interesting, the character type will be ignored. While  
306
some database engines have differences in performance between the varchar and text data types,  
307
this tip from the linked postgres docs is good to keep in mind:
308
309
.. note::  
310
"There are no performance differences between these three types, apart from the increased storage size  
311
when using the blank-padded type. While character(n) has performance advantages in some other database  
312
systems, it has no such advantages in PostgreSQL. In most situations text or character varying should  
313
be used instead."
314
315
The "blank-padded" type mentioned in that quote is the character type, so for our purposes there is no  
316
difference in performance between varchar and text.
317
318
Used in:  
319
\- \`pulp_rpm/plugins/pulp_rpm/plugins/db/models.py\`  
320
\- \`pulp_rpm/plugins/pulp_rpm/plugins/db/fields.py\`  
321
\- \`pulp_ostree/plugins/pulp_ostree/plugins/db/model.py\`  
322
\- \`pulp_docker/plugins/pulp_docker/plugins/models.py\`  
323
\- \`pulp_puppet/pulp_puppet_plugins/pulp_puppet/plugins/db/models.py\`  
324
\- \`pulp/server/pulp/server/db/model/\_\_init\_\_.py\`  
325
\- \`pulp/server/pulp/server/db/fields.py\`  
326
\- \`pulp_python/plugins/pulp_python/plugins/models.py\`
327
328
CharField  
329
\*****\*****\*
330
331
https://docs.djangoproject.com/en/1.8/ref/models/fields/#charfield
332
333
Represented by a varchar field in postgres, the max_length argument is required.
334
335
When the maximum length of a string is known, such as when storing hash values of a known type (or  
336
types), this is the field to use. String length validation is done at the database level.
337
338
TextField  
339
\*****\*****\*
340
341
https://docs.djangoproject.com/en/1.8/ref/models/fields/#textfield
342
343
Represented by a text field in postgres.
344
345
When the maximum length of a string is unknown, such as when storing large chunks of text like errata  
346
descriptions/summaries, this is the field to use.
347
348
IntField  
349
\^<sup>\^\^\^\^</sup>\^
350
351
http://docs.mongoengine.org/apireference.html#mongoengine.fields.IntField
352
353
There are more numeric types supported by postgres + Django than are offered by MongoEngine,  
354
so converting from one of these MongoEngine fields to a postgres field should take  
355
the available Django field types into account to ensure that the most appropriate  
356
postgres data type is being used.
357
358
https://www.postgresql.org/docs/current/static/datatype-numeric.html
359
360
The only known MongoEngine FloatField in Pulp is a timestamp field on the Distribution document,  
361
which could reasonably be converted to a DateTimeField.
362
363
Used in:  
364
\- \`pulp_rpm/plugins/pulp_rpm/plugins/db/models.py\`  
365
\- \`pulp_docker/plugins/pulp_docker/plugins/models.py\`  
366
\- \`pulp/server/pulp/server/db/model/\_\_init\_\_.py\`
367
368
IntegerField, SmallIntegerField, BigIntegerField  
369
\*****\*****\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*
370
371
https://docs.djangoproject.com/en/1.8/ref/models/fields/#integerfield  
372
https://docs.djangoproject.com/en/1.8/ref/models/fields/#smallintegerfield  
373
https://docs.djangoproject.com/en/1.8/ref/models/fields/#bigintegerfield
374
375
2-byte, 4-byte, and 8-byte (respectively) storage for signed integers.
376
377
PositiveIntegerField, PositiveSmallIntegerField  
378
\*****\*****\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*
379
380
https://docs.djangoproject.com/en/1.8/ref/models/fields/#positiveintegerfield  
381
https://docs.djangoproject.com/en/1.8/ref/models/fields/#positivesmallintegerfield
382
383
Positive-only variants of SmallIntegerField and IntegerField. These use the  
384
same postgres data types as their non-"Positive" counterparts, but use database  
385
validation to enforce values \>= 0.
386
387
FloatField  
388
\^<sup>\^\^\^\^\^\^</sup>\^
389
390
http://docs.mongoengine.org/apireference.html#mongoengine.fields.FloatField
391
392
Also numeric types, just like IntField and LongField, but there are some python representation options  
393
when it comes to floats that are available in django fields.
394
395
https://www.postgresql.org/docs/current/static/datatype-numeric.html
396
397
Used in:  
398
\- \`pulp_rpm/plugins/pulp_rpm/plugins/db/models.py\`
399
400
FloatField  
401
\*****\*****\*\*
402
403
https://docs.djangoproject.com/en/1.8/ref/models/fields/#floatfield
404
405
Stored as the "double precision" data type, using 8 bytes of storage. Represents the python "float"  
406
type.
407
408
DecimalField  
409
\*****\*****\*\*\*\*
410
411
https://docs.djangoproject.com/en/1.8/ref/models/fields/#decimalfield
412
413
Stored as the "numeric" data type, storage size varies based on the field precision declared when the  
414
field is created. Very similar to FloatField, but values are represented by the python  
415
"decimal.Decimal" type. Use this field instead of FloatField in cases where the "decimal.Decimal"  
416
type is more appropriate.
417
418
For reference: https://docs.python.org/3/library/decimal.html
419
420
The postgres docs state that "The actual storage requirement is two bytes for each group of four  
421
decimal digits, plus three to eight bytes overhead," so there's no obvious storage efficiency benefit  
422
the be gained by using this field.
423
424
BooleanField  
425
\^<sup>\^\^\^\^\^\^\^\^</sup>\^
426
427
http://docs.mongoengine.org/apireference.html#mongoengine.fields.BooleanField
428
429
A normal BooleanField, represented a True/False value in python.
430
431
https://www.postgresql.org/docs/current/static/datatype-boolean.html
432
433
Used in:  
434
\- \`pulp_rpm/plugins/pulp_rpm/plugins/db/models.py\`  
435
\- \`pulp/server/pulp/server/db/model/\_\_init\_\_.py\`
436
437
BooleanField, NullBooleanField  
438
\*****\*****\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*
439
440
Represented by the "boolean" data type in postgres. "BooleanField" stores only True or False,  
441
and cannot be null/None, so a default must be specified. The "NullBooleanField" alternative  
442
additionally allows for null/None values, useful in cases where a boolean value might be  
443
unknown, or not required.
444
445
https://docs.djangoproject.com/en/1.8/ref/models/fields/#booleanfield  
446
https://docs.djangoproject.com/en/1.8/ref/models/fields/#nullbooleanfield
447
448
DateTimeField, UTCDateTimeField, ISO8601StringField  
449
\^<sup>\^\^\^\^\^\^\^\^\^\^\^\^\^\^\^\^\^\^\^\^\^\^\^\^\^\^\^\^\^\^\^\^\^\^\^\^\^\^\^\^\^\^\^\^\^\^\^</sup>\^
450
451
http://docs.mongoengine.org/apireference.html#mongoengine.fields.DateTimeField
452
453
All mongoengine DateTimeFields should, at this point, be storing UTC datetime  
454
stamps, represented in python as "datetime.datetime" instances. UTCDateTimeField and  
455
ISO8601StringField are custom fields with special behavior for storage, but  
456
all datetimes should be stored in postgres as postgres's native data type, so the only  
457
Django field type we should be using for all of these mongo fields is DateTimeField.  
458
Custom serialization/deserialization of datetime data should be done at the API layer.
459
460
https://www.postgresql.org/docs/current/static/datatype-datetime.html
461
462
Used in:  
463
\- \`pulp_ostree/plugins/pulp_ostree/plugins/db/model.py\`  
464
\- \`pulp/server/pulp/server/db/model/\_\_init\_\_.py\`  
465
\- \`pulp/server/pulp/server/db/fields.py\`
466
467
DateTimeField  
468
\*****\*****\*\*\*\*\*
469
470
https://docs.djangoproject.com/en/1.8/ref/models/fields/#datetimefield
471
472
Represented in postgres as the "timestamp with time zone" data type. Django is configured  
473
to use the UTC timezone, so tz-aware datetime objects will be properly converted to  
474
UTC timestamps when stored, our custom UTCDateTimeField is not required with Django.
475
476
DateField, TimeField  
477
\^<sup>\^\^\^\^\^\^\^\^\^\^\^\^\^\^\^\^</sup>\^
478
479
MongoEngine does not provide equivalents for these field types, but they're worth mentioning  
480
in the event that only a date or time component of a datetime object needs to be stored.
481
482
https://docs.djangoproject.com/en/1.8/ref/models/fields/#datefield
483
484
DateField represents the postgres "date" data type, and is the "datetime.date" type in python.
485
486
https://docs.djangoproject.com/en/1.8/ref/models/fields/#timefield
487
488
TimeField represents the postgres "time" data type, and is the "datetime.time" type in python.  
489
Unlike DateTimeField, TimeField appears to be unaware of time zones; the column type is  
490
"time with
491
492
UUIDField  
493
\^<sup>\^\^\^\^\^</sup>\^
494
495
http://docs.mongoengine.org/apireference.html#mongoengine.fields.UUIDField
496
497
UUIDs, represented by instances of the "uuid.UUID" data type.
498
499
Used in:  
500
\- \`pulp/server/pulp/server/db/model/\_\_init\_\_.py\`
501
502
UUIDField  
503
\*****\*****\*
504
505
https://docs.djangoproject.com/en/1.8/ref/models/fields/#uuidfield
506
507
Postgres has native support for UUIDs with the "uuid" data type, storing the value  
508
as the UUID's 128-bit/16-byte value, rather than the UUID string representation.
509
510
All models in Pulp 3 also use a UUIDField as their Primary Key by default.
511
512
ListField  
513
\^<sup>\^\^\^\^\^</sup>\^
514
515
http://docs.mongoengine.org/apireference.html#mongoengine.fields.ListField
516
517
In general, elements of ListField arrays should be turned into their own  
518
Django Model, with a ForeignKey relationship back to the Model that originally  
519
contained the ListField.
520
521
A sort of case-study regarding converting ListFields to models can be found in the  
522
"ListField Conversion Example" section of this document.
523
524
Used in:  
525
\- \`pulp_rpm/plugins/pulp_rpm/plugins/db/models.py\`  
526
\- \`pulp_docker/plugins/pulp_docker/plugins/models.py\`  
527
\- \`pulp_puppet/pulp_puppet_plugins/pulp_puppet/plugins/db/models.py\`  
528
\- \`pulp/server/pulp/server/db/model/\_\_init\_\_.py\`
529
530
DictField  
531
\^<sup>\^\^\^\^\^</sup>\^
532
533
http://docs.mongoengine.org/apireference.html#mongoengine.fields.DictField
534
535
There are many and varied instances of DictFields in Pulp. DictFields can usually  
536
either be reduced to key/value stores, or should (like with ListField) be turned  
537
into Django Models that ForeignKey back to the Model that originally contained the  
538
DictField. For the case of key/value stores, see the "Arbitrary User Data" section  
539
for details on how to handle that case.
540
541
Used in:  
542
\- \`pulp_rpm/plugins/pulp_rpm/plugins/db/models.py\`  
543
\- \`pulp_ostree/plugins/pulp_ostree/plugins/db/model.py\`  
544
\- \`pulp/server/pulp/server/db/model/\_\_init\_\_.py\`
545
546
UUID Primary Keys  
547
\-----------------
548
549
Postgres has native support for the UUID datatype, as does Django, making a UUID a viable option  
550
for primary keys. UUIDs are already being used at the de-facto Primary Key of the MongoEngine  
551
ContentUnit. Keeping these UUIDs when migrating to Postgres makes it so that users integrating with  
552
Pulp will be able to keep any references they may have in their own data stores to Pulp ContentUnit  
553
by their existing UUID PK.
554
555
Master and Detail ContentUnit Types  
556
\-----------------------------------
557
558
The "master" ContentUnit model (ContentUnit itself) has some special behaviors added to accomodate  
559
the master-detail inheritance implementation. ContentUnit instance have a \`cast\` method that will  
560
return a "detail" instance of a ContentUnit type, e.g. the RPM instance for that ContentUnit. Calling  
561
\`cast\` on a detail instance will return that instance, making \`cast\` idempotent.
562
563
Similarly, all ContentUnits have a \`content_unit\` property that, when accessed, will always be the  
564
master ContentUnit instance. It functions similarly to \`cast\`, in that it is idempotent. This is a  
565
property, not a method, because all detail ContentUnit instances are already ContentUnits in an  
566
object-oriented sense, whereas \`cast\`-ing ContentUnits will most likely result in a database JOIN  
567
operation.
568
569
ListField Conversion Example (Errata)  
570
\-------------------------------------
571
572
In Pulp 2, the Errata model has many ListFields associated with it:  
573
\- references, a list of items to which this Errata refers, such as BZ bugs and CVEs  
574
\- pkglist, a list of package collections (themselves a list) referred to by this errata
575
576
As a result, both "references" and "pkglist" should become their own Model with a corresponding table  
577
in the database with a ForeignKey relationship back to Errata. Furthermore, because the "pkglist"  
578
element in updateinfo.xml can contains package collections, another Model is needed to represent  
579
those package collections, which then has a ForeignKey relationship back to the pkglist that contains  
580
it.
581
582
To sum up, the single Pulp 2 Errata model, with its two ListFields, becomes four Django Models:  
583
\- Errata
584
585
~~~
586
- ErrataReference - Exposed on Errata instances at the "references" attribute
587
- ErrataCollection - Exposed on Errata instances as the "pkglist" attribute
588
~~~
589
590
~~~
591
- ErrataPackage - Exposed on ErrataCollection instances as the "packages" attribute
592
~~~
593
594
These models (probably!) meet the requirements for errata:  
595
\- Pulp can store all data found in errata updateinfo XML files when syncing repos.  
596
\- Pulp can generate equivalent updateinfo XML files when publishing repos.