.. _subclassing-models: Models ====== For the most part, models provided by plugin writers are just regular `Django Models `_. .. note:: One slight variation is that the validation is primarily handled in the Django Rest Framework Serializer. ``.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 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. .. note:: It is required to declare the ``default_related_name``. The TYPE class attribute is used for filtering purposes. .. code-block:: python 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 ``digest`` using Django's ``TextField``. After adding/modifying a model, you can make and run database migrations with: .. code-block:: bash pulpcore-manager makemigrations $PLUGIN_APP_LABEL pulpcore-manager migrate .. warning:: Do not use settings directly in the model fields, it will lead to a data migration that is specific to the users installation in case those settings change. If you recognize this syntax, it is because pulpcore-manager is ``manage.py`` configured with ``DJANGO_SETTINGS_MODULE="pulpcore.app.settings"``. You can use it anywhere you normally would use ``manage.py`` or ``django-admin``. Uniqueness ~~~~~~~~~~ Model uniqueness (which will also be used as the natural key) is defined by an inner ``class Meta``. Pulp Core enforces uniqueness constraints at the database level. Adding to the simplified ``FileContent`` above: .. code-block:: python 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. .. code-block:: python 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 ``relative_path`` and ``digest`` together. ForeignKey Gotchas ~~~~~~~~~~~~~~~~~~ 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'"