Django admin site returns MultipleObjectsReturned exception with inspectdb imported legacy database and composite primary key
Используя inspectdb, я импортировал в django унаследованную базу данных, содержащую сущности с составными первичными ключами. Схема базы данных содержит около 200 различных сущностей, и inspectdb оказался весьма удобен в этой ситуации.
Это схема в mysql:
CREATE TABLE `mymodel` (
`id` bigint(20) unsigned NOT NULL DEFAULT '0',
`siteid` bigint(20) unsigned NOT NULL DEFAULT '0',
...
PRIMARY KEY (`siteid`,`id`),
...
Следующая автогенерируемая модель в django (импортированная с помощью python manager.py inspectdb)
class Mymodel(models.Model):
id = models.PositiveBigIntegerField()
siteid = models.PositiveBigIntegerField(primary_key=True)
...
class Meta:
managed = False
db_table = 'mymodel'
unique_together = (('siteid', 'id'),
Я зарегистрировал все модели на сайте администратора, используя следующий подход:
from django.contrib import admin
from django.apps import apps
app = apps.get_app_config('appname')
for model_name, model in app.models.items():
admin.site.register(model)
После того, как вся работа выполнена, я перехожу на сайт администратора и нажимаю на любой объект в разделе "mymodel", и возвращается следующее исключение:
appname.models.Content.MultipleObjectsReturned: get() returned more than one Mymodel-- it returned more than 20!
Очевидно (по крайней мере, мне так кажется), что админ использует siteid для получения объекта, но он должен использовать unique_together из класса Meta.
Есть предложения, как я могу решить эту проблему с помощью общей конфигурации и заставить модуль admin сайта запрашивать с помощью unique_together?
Да, вы можете решить эту проблему, но приложите немного больше усилий.
Сначала вы выделяете класс model-admin для модели Mymodel и настраиваете метод класса model-admin:
Поскольку админ django встроил change url в класс ChangeList, мы можем создать собственный класс Changelist, например MymodelChangelist и передать значение поля
id
в качестве параметров запроса. Мы будем использовать значение поляid
для получения объекта.Override get_object() метод для использования пользовательского запроса для получения объекта из queryset
.Override get_changelist() метод model-admin для установки вашего пользовательского Changelist класса
.Определите метод save_model() для явного сохранения объекта.
admin.py
class MymodelChangelist(ChangeList):
# override changelist class
def url_for_result(self, result):
id = getattr(result, 'id')
pk = getattr(result, self.pk_attname)
url = reverse('admin:%s_%s_change' % (self.opts.app_label,
self.opts.model_name),
args=(quote(pk),),
current_app=self.model_admin.admin_site.name)
# Added `id` as query params to filter queryset to get unique object
url = url + "?id=" + str(id)
return url
@admin.register(Mymodel)
class MymodelAdmin(admin.ModelAdmin):
list_display = [
'id', 'siteid', 'other_model_fields'
]
def get_changelist(self, request, **kwargs):
"""
Return the ChangeList class for use on the changelist page.
"""
return MymodelChangelist
def get_object(self, request, object_id, from_field=None):
"""
Return an instance matching the field and value provided, the primary
key is used if no field is provided. Return ``None`` if no match is
found or the object_id fails validation.
"""
queryset = self.get_queryset(request)
model = queryset.model
field = model._meta.pk if from_field is None else model._meta.get_field(from_field)
try:
object_id = field.to_python(object_id)
# get id field value from query params
id = request.GET.get('id')
return queryset.get(**{'id': id, 'siteid': object_id})
except (model.DoesNotExist, ValidationError, ValueError):
return None
def save_model(self, request, obj, form, change):
cleaned_data = form.cleaned_data
if change:
id = cleaned_data.get('id')
siteid = cleaned_data.get('siteid')
other_fields = cleaned_data.get('other_fields')
self.model.objects.filter(id=id, siteid=siteid).update(other_fields=other_fields)
else:
obj.save()
Теперь вы можете обновить любые объекты, а также добавить новый объект. Но в одном случае вы не можете добавить - siteid which is already added because of primary key validation