Оптимизация архитектуры django для ускорения просмотра списка в админке
Я занимаюсь разработкой среднего сайта для своей компании с большим объемом данных (исследовательские публикации, сотни сотрудников и т. Д.) И ограничениями безопасности, что заставило меня задуматься об использовании django-guardian для обработки прав доступа к объектам. Но теперь я понимаю, что это может быть медленно внутри админа.
Мы уже внедрили кэш Redis, который, кажется, работает довольно хорошо, но загрузка больших списков (сотни элементов) все еще занимает много времени.
Пока что мы используем следующие настройки:
django 1.5.5
django-cms 2.4.3
django-redis-cache 0.10.2
django-guardian 1.1.1
hiredis 0.1.2
redis 2.9.1
python 2.7.5
postgresql
centos
В качестве примера, это модуль Person, который загружается в виде списка (не как суперпользователь, в этом случае он довольно быстрый: по этой причине я думаю, что проблема заключается в множественных отношениях django-guardian):
class Person(models.Model):
TYPE_CHOICES = (
('S', _('Student')),
('E', _('Researcher')),
)
class Meta:
permissions = (
('view_person', _('View person')),
)
index_together = (
('last_name', 'first_name'),
)
# Relations with other entities
topics = models.ManyToManyField('topics.Topic', blank=True, related_name='people')
competences = models.ManyToManyField('staff.Competence', blank=True, related_name='people', db_index=True)
# Person properties
cmsuser = models.OneToOneField(User, blank=True, related_name='person', null=True, db_index=True)
sebra_username = models.CharField(max_length=20, blank=True, db_index=True)
first_name = models.CharField(_('first name'), max_length=30, blank=True, db_index=True)
last_name = models.CharField(_('last name'), max_length=30, blank=True, db_index=True)
email = models.EmailField(_('email address'), blank=True, db_index=True)
username = models.CharField(_('username'), max_length=30, unique=True, db_index=True)
avatar = models.ImageField(upload_to='img/avatar/', blank=True)
web = models.URLField(_("web site"), blank=True)
cristin_profile = models.URLField(_('link to cristin profile'), blank=True)
twitter = models.CharField(_("twitter username"), max_length=20, blank=True)
telephone = models.CharField(
blank=True,
max_length=validators.MAX_LENGTH_PHONE,
validators=[validators.validate_phone_format]
)
telephone_country_code = models.ForeignKey(Country, null=True, blank=True, related_name='phone_person')
mobile = models.CharField(
blank=True,
max_length=validators.MAX_LENGTH_PHONE,
validators=[validators.validate_phone_format]
)
mobile_country_code = models.ForeignKey(Country, null=True, blank=True, related_name='mobile_person')
address = models.TextField(max_length=255, blank=True)
cv = models.FileField(_('Curriculum Vitae'), upload_to='attachments/cv/', blank=True)
vcard = models.FileField(_('Vcard'), upload_to='attachments/vcard/', blank=True)
person_type = models.CharField(choices=TYPE_CHOICES, max_length=1, blank=True)
extract = RichTextField(_('person extract'), blank=True, default='')
slug = AutoSlugField(populate_from=('first_name', 'last_name'))
def __unicode__(self):
if len(self.first_name) + len(self.last_name):
return '%s %s' % (self.first_name, self.last_name)
return self.username
def clean(self):
super(Person, self).clean()
if self.sebra_username.strip():
# here goes validation and checks on the related objects
def get_absolute_url(self):
if PersonDepartmentMembership.objects.filter(active__exact=True, person__exact=self):
return reverse('staff:profile_slug', kwargs={'slug': self.slug})
return ''
Я понимаю, что узкое место может быть также в моем классе администратора. Это тот, который мы используем:
class PersonAdmin(ModelAdmin):
fields = (
'sebra_username', 'first_name', 'last_name', 'avatar', 'email', 'person_type', 'extract',
'topics', 'competences', 'web', 'cristin_profile', 'twitter', 'telephone_country_code',
'telephone', 'address', 'mobile_country_code', 'mobile', 'cv', 'vcard'
)
search_fields = ('sebra_username', 'first_name', 'last_name', 'email', 'departments__name')
list_filter = ('departments__name', 'research_groups__group_name', 'projects__project_name')
inlines = (SomeInline,)
class Media:
js = (
settings.STATIC_URL + 'js/jquery-1.9.0.min.js',
settings.STATIC_URL + 'js/jquery-ui-1.9.2.custom.min.js',
'modeltranslation/js/tabbed_translation_fields.js',
)
css = {
'screen': ('modeltranslation/css/tabbed_translation_fields.css',),
}
def formfield_for_manytomany(self, db_field, request=None, **kwargs):
if db_field.name == 'topics':
kwargs['queryset'] = get_objects_for_user(user=request.user, perms=('topics.view_topic',))
elif db_field.name == 'competences':
kwargs['queryset'] = get_objects_for_user(user=request.user, perms=('staff.view_competence',))
return super(PersonAdmin, self).formfield_for_manytomany(db_field, request, **kwargs)
def queryset(self, request):
if request.user.is_superuser:
return super(PersonAdmin, self).queryset(request)
return get_objects_for_user(user=request.user, perms=('staff.change_person',)).order_by('last_name')
def has_add_permission(self, request):
return request.user.has_perm('staff.add_person')
def has_delete_permission(self, request, obj=None):
return request.user.has_perm('staff.delete_person', obj)
def has_change_permission(self, request, obj=None):
return request.user.has_perm('staff.change_person', obj)
Не могли бы вы дать мне какой-нибудь совет или предложить какое-либо возможное решение, которое мы могли бы интегрировать в интерфейс администратора?:-)
Заранее спасибо!
РЕДАКТИРОВАТЬ:
Используя django-debug-toolbar, я вижу, что очень мало запросов к django-guardian и довольно быстрых (все ниже 6 мс). С другой стороны, у меня более 7500 запросов на просмотр списка из 263, что замедляет представление до 46 секунд для генерации. Почти все они находятся в моей определенной модели для загрузки (я думаю) бесполезных данных: я полагаю, что нужны только имя и идентификатор объекта.
Как я могу ограничить объем данных, загружаемых в методе queryset()? Благодарю.
1 ответ
Если вы выполняете 7500 запросов, ваша проблема, вероятно, в том, что вы не загружаете связанные объекты, которые вам нужны - вот что я бы посмотрел:
- Есть ли в вашей модели методы доступа к связанным объектам?
- У вас есть вещи в вашем
list_display
настройка доступа к связанным объектам?
Либо исключите призывы к этим вещам, либо посмотрите на select_related
,
Кроме того - попробуйте изменить нумерацию ваших просмотров администратора - если вы уменьшите количество отображаемых записей - сколько там запросов? Это даст вам представление о том, сколько из этих проблем у вас есть.