Ability to customise model Meta & migration generation
- Dominant language
- No language data
- Stars
- 188
- Forks
- 7
- PR merge metrics
- No merged PRs in 30d
Description
### Code of Conduct
- [x] I agree to follow Django's Code of Conduct
### Feature Description
Allow the ability to build supporting functionality around a model by customising Meta & migration generation without monkey patching.
### Problem
### What does "build functionality around a model" mean?
Basically extending the model in ways that the Django framework does not yet support but may be supported by the underlying database (…or even just other non-database functionality not yet conceived)
Some common use cases I've come across:
- Initialise with prerequisite data
- Triggers
- Row Level Security
- Temporal tables
- Database views
- Scheduled tasks (eg with pg_cron)
- Adding custom functions, types & other tidbits related to the model
### The current ways to build functionality around a model
**1. Custom migrations:** manually written and added to `migrations/`. This can be tedious and doesn't lend itself to 3rd party libraries well. Code colocality is quite low. A lot of Django developers I come across often admit that they will forget about custom migrations added, or don't realise their presence when being brought onto a project. Newer developers often don't even know that custom migrations are a thing.
I've seen some folks do things like this to help with the discoverability:
```python
class Foo(Model):
...
class Meta:
...
# The following custom migrations for this model have been added:
# 1. Additional composite foreign key to enforce tenancy (see ....)
# 2. ... etc ...
```
**2. Abuse `Meta.constraints`:** Constraints aren't really designed for a general purpose model customisation **but** it seems to work well enough that I often use this hack to do this. See: https://github.com/shangxiao/stupid-django-tricks/tree/master/abusing_constraints
**3. Monkey Patching:** Take a look at [django-pgtrigger](https://github.com/AmbitionEng/django-pgtrigger) works to see what patching needs to happen.
### Request or proposal
proposal
### Additional Details
### Some examples of how this would be beneficial
#### Initial data
```python
class ValidDomain(Model):
domain = CharField()
class Meta:
initial_data = [
# sad face that we can't refer to ValidDomain within itself
Self(domain="foo.com.hk"),
Self(domain="bar.com"),
]
```
#### System-versioned tables with MariaDB
```python
class Company(Model):
name = CharField(...)
...
class Meta:
system_versioned = True
```
#### Views
```python
class Account(Model):
name = CharField()
is_active = BooleanField()
class Meta:
views = [
View(name="ActiveAccounts", condition=Q(is_active=True), materialized=False),
]
# alternatively
class ActiveAccount(Model):
name = CharField()
class Meta:
db_view = True
query = Account.objects.filter(is_active=True)
materialized=False
```
### Implementation Suggestions
Some notes here on what may need to change, garnered from django-pgtrigger:
This would require:
1. allowing `django.db.models.options.DEFAULT_NAMES` to be extendable
2. allowing `django.db.models.migrations.state.DEFAULT_NAMES` to be extendable
3. allowing `MigrationAutodetector` to be extendable. Just looking over the `_detect_changes()` it all looks very order specific and no obvious place to hook into but that might just be up to the user to determine the most appropriate place, a special hook may not be necessary.
We can already do:
1. Define custom migration operations
2. Extend DB backends to cater for backend specific syntax, like outright table creation for eg
**Edit** I missed this but [ticket #35656](https://code.djangoproject.com/ticket/35656) allowed specifying the autodetector class as a class attribute on the `makemigrations` & `migrate` commands in order to allow customisation in subclassed commands.
Contributor guide
No contributing guide indexed for this repository
Assessment
This issue has not been assessed yet.