【问题标题】:Django: Setting current user on a model to use in InlineModelAdminDjango:在模型上设置当前用户以在 InlineModelAdmin 中使用
【发布时间】:2012-08-21 09:00:43
【问题描述】:

我有一些这样的模型:

class BaseModel(models.Model):
    created_by = models.ForeignKey(User, related_name="%(app_label)s_%(class)s_created")
    created_date = models.DateTimeField(_('Added date'), auto_now_add=True)
    last_updated_by = models.ForeignKey(User, related_name="%(app_label)s_%(class)s_updated")
    last_updated_date = models.DateTimeField(_('Last update date'), auto_now=True)

    class Meta:
        abstract = True

class Image(BaseModel):
    content_type = models.ForeignKey(ContentType)
    object_id = models.PositiveIntegerField()
    content_object = generic.GenericForeignKey('content_type', 'object_id')

    name = models.CharField(_('Item name'), max_length=200, blank=True)
    image = models.ImageField(_('Image'), upload_to=get_upload_path)

    def save(self, *args, **kwargs):
        if self.image and not GALLERY_ORIGINAL_IMAGESIZE == 0:
            width, height = GALLERY_ORIGINAL_IMAGESIZE.split('x')
            super(Image, self).save(*args, **kwargs)

            filename = os.path.join( settings.MEDIA_ROOT, self.image.name )
            image = PILImage.open(filename)

            image.thumbnail((int(width), int(height)), PILImage.ANTIALIAS)
            image.save(filename)

        super(Image, self).save(*args, **kwargs)

class Album(BaseModel):
    name = models.CharField(_('Album Name'), max_length=200)
    description = models.TextField(_('Description'), blank=True)
    slug = models.SlugField(_('Slug'), max_length=200, blank=True)
    status = models.SmallIntegerField(_('Status'),choices=ALBUM_STATUSES)

    images = generic.GenericRelation(Image)

我为我的所有模型使用 BaseModel 抽象模型来跟踪保存和更新日志。我可以使用 ModelAdmin 类自动设置用户字段:

class BaseAdmin(admin.ModelAdmin):
    def save_model(self, request, obj, form, change):
        if not change:
            obj.created_by = request.user

        obj.last_updated_by = request.user
        obj.save()


class AlbumAdmin(BaseAdmin):
    prepopulated_fields = {"slug": ("name",)}
    list_display = ('id','name')
    ordering = ('id',)

这行得通。所有 BaseAdmin 字段都会自动填写。但我想通过内联将图像添加到相册。所以,我像这样更改我的 admin.py:

from django.contrib.contenttypes import generic

class ImageInline(generic.GenericTabularInline):
    model = Image
    extra = 1

class AlbumAdmin(BaseAdmin):
    prepopulated_fields = {"slug": ("name",)}
    list_display = ('id','name')
    ordering = ('id',)

    inlines = [ImageInline,]

当我保存页面时,出现错误:gallery_image.created_by_id may not be NULL 在图像模型保存方法的第一行 super(Image, self).save(*args, **kwargs) 上。我知道这是因为 GenericTabularInline 类没有要覆盖的“save_model”方法。

那么,问题是,如何在 InlineModelAdmin 类上覆盖 save 方法并设置当前用户?

【问题讨论】:

标签: django django-models django-forms django-admin


【解决方案1】:

我找到了另一个问题的解决方案:https://stackoverflow.com/a/3569038/198062

所以,我改变了我的 BaseAdmin 模型类,它就像一个魅力:

from models import BaseModel

class BaseAdmin(admin.ModelAdmin):
    def save_model(self, request, obj, form, change):
        if not change:
            obj.created_by = request.user

        obj.last_updated_by = request.user
        obj.save()

    def save_formset(self, request, form, formset, change):
        instances = formset.save(commit=False)

        for instance in instances:
            if isinstance(instance, BaseModel): #Check if it is the correct type of inline
                if not instance.created_by_id:
                    instance.created_by = request.user

                instance.last_updated_by = request.user            
                instance.save()

请注意,您必须为包含内联的 ModelAdmin 扩展相同的抽象类才能使用此解决方案。或者您可以将该 save_formset 方法添加到专门包含内联的 ModelAdmin。

【讨论】:

    【解决方案2】:

    我希望在我的所有模型上设置用户,无论它们在何处/如何被操纵。我花了很长时间才弄明白,但这里是如何使用中间件在任何模型上设置它:

    """Add user created_by and modified_by foreign key refs to any model automatically.
       Almost entirely taken from https://github.com/Atomidata/django-audit-log/blob/master/audit_log/middleware.py"""
    from django.db.models import signals
    from django.utils.functional import curry
    
    class WhodidMiddleware(object):
        def process_request(self, request):
            if not request.method in ('GET', 'HEAD', 'OPTIONS', 'TRACE'):
                if hasattr(request, 'user') and request.user.is_authenticated():
                    user = request.user
                else:
                    user = None
    
                mark_whodid = curry(self.mark_whodid, user)
                signals.pre_save.connect(mark_whodid,  dispatch_uid = (self.__class__, request,), weak = False)
    
        def process_response(self, request, response):
            signals.pre_save.disconnect(dispatch_uid =  (self.__class__, request,))
            return response
    
        def mark_whodid(self, user, sender, instance, **kwargs):
            if instance.has_attr('created_by') and not instance.created_by:
                instance.created_by = user
            if instance.has_attr('modified_by'):
                instance.modified_by = user
    

    【讨论】:

    • 并行处理多个请求时,您可能会陷入竞争状态,其中mark_whodid 被不同的用户调用两次。假设在当前线程中回调了信号,则可以通过将请求存储在 threadlocal 中并将其发送给侦听器(mark_whodid)来消除这种竞争条件
    【解决方案3】:

    除了mindlace的回答;当 created_by 字段恰好有 null=True 时,not instance.created_by 会出错。我使用instance.created_by_id is None 来避免这种情况。

    (我宁愿将此作为对答案的评论,但我目前的声誉不允许...)

    【讨论】:

      猜你喜欢
      • 2017-05-08
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2017-11-04
      • 2011-07-22
      • 1970-01-01
      • 1970-01-01
      • 2012-06-15
      相关资源
      最近更新 更多