For the most part, models provided by plugin writers are just regular Django Models.
One slight variation is that the validation is primarily handled in the Django Rest Framework
.clean() is not called.
- Most plugins will implement:
- model(s) for the specific content type(s) used in the plugin, should be subclassed from Content model
- model(s) for the plugin specific remote(s), should be subclassed from Remote model
- model(s) for the plugin specific publisher(s), should be subclassed from Publisher model
Adding Model Fields¶
Each subclassed Model will typically store attributes that are specific to the content type. These
attributes need to be added to the model as
fields. You can use any of Django’s field types
for your fields. See the Django field documentation, for more in-depth information on
using these fields.
One of Pulp’s goals is to work correctly on multiple databases. It is probably best to avoid fields that are not database agnostic. See Database Gotchas below.
It is required to declare the
The TYPE class attribute is used for filtering purposes.
class FileContent(Content): """ The "file" content type. Fields: digest (str): The SHA256 HEX digest. """ TYPE = 'file' digest = models.TextField(null=False) class Meta: default_related_name = "%(app_label)s_%(model_name)s"
Here we create a new field using use Django’s
TextField. After adding/modifying a model, you
can make and run database migrations with:
django-admin makemigrations <plugin_app_label> django-admin migrate
If you recognize this syntax, it is because django-admin is used with the same interace as
admin, but has additional commands.
Model uniqueness (which will also be used as the natural key) is defined by an inner
Meta. Pulp Core enforces uniqueness constraints at the database level.
Adding to the simplified
class FileContent(Content): """ The "file" content type. Content of this type represents a single file uniquely identified by path and SHA256 digest. Fields: digest (str): The SHA256 HEX digest. """ TYPE = 'file' digest = models.TextField(null=False) class Meta: # Note the comma, this must be a tuple. unique_together = ('digest',) default_related_name = "%(app_label)s_%(model_name)s"
In this example the Content’s uniqueness enforced on a single field
digest. For a multi-field
uniqueness, simply add other fields.
class FileContent(Content): """ The "file" content type. Content of this type represents a single file uniquely identified by path and SHA256 digest. Fields: relative_path (str): The file relative path. digest (str): The SHA256 HEX digest. """ TYPE = 'file' relative_path = models.TextField(null=False) digest = models.TextField(null=False) class Meta: default_related_name = "%(app_label)s_%(model_name)s" unique_together = ( 'relative_path', 'digest', )
The example above ensures that content is unique on
Plugin writers should be aware that certain things may not be database agnostic. Here is a list of a few things we’ve found.
unique on a
TextField will cause problems when using MySQL/MariaDB:
name = models.TextField(db_index=True) # BLOB/TEXT column 'name' used in key specification without a key length
For this reason, we recommend using
CharField in cases where the field needs to be indexed.
Also, the max length for
CharField in MySQL/MariaDB is 255:
name = models.CharField(max_length=256) # MyModel.name: (mysql.E001) MySQL does not allow unique CharFields to have a max_length > 255
In general, we recommend testing your plugins against as many database systems as possible. Travis or other continuous integration environments can also be used to verify that your plugin is database agnostic.
The orphan cleanup operation performs mass-deletion of Content units that are not associated with
any repository. Any ForeignKey relationships that refer to Content with a deletion relationship of
PROTECT will cause Orphan cleanup errors like:
django.db.models.deletion.ProtectedError: ("Cannot delete some instances of model 'MyContent' because they are referenced through a protected foreign key: 'MyOtherContent.mycontent'"