优化django架构以加速管理员中的列表视图

时间:2014-02-26 12:12:00

标签: python django redis django-admin django-guardian

我正在为我的公司开发一个中型网站,包含大量数据(研究出版物,数百名员工等)和安全限制,这让我想到使用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 ''

我理解瓶颈可能也在我的Admin类中。这是我们正在使用的那个:

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的查询,而且速度非常快(都低于6ms)。另一方面,对于263的列表视图,我有超过7500个查询,这会使视图减慢到46秒。几乎所有这些都在我定义的模型中,加载(我认为)无用的数据:我想只需要名称和对象id。

如何限制queryset()方法中加载的数据量?感谢。

1 个答案:

答案 0 :(得分:1)

如果你正在执行7500个查询,那么你的问题可能就是你没有加载你需要的相关对象 - 这就是我所看到的:

  1. 您的模型上是否有访问相关对象的方法?
  2. 您的list_display设置中是否有访问相关对象的项目?
  3. 要么取消对这些内容的调用,要么查看select_related

    另外 - 尝试更改pagination of your admin views - 如果减少显示的记录数 - 减少了多少查询?这会让你知道你有多少这些问题。