0

I have some models like that:

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)

I use BaseModel abstract model for my all models to track save and update logs. I can use ModelAdmin class to set user fields automatically:

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',)

That works. All BaseAdmin fields are filled automatically. But I want to add Images to Albums by Inline. So, I change my admin.py like that:

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,]

When I save page, I get an error: gallery_image.created_by_id may not be NULL on first super(Image, self).save(*args, **kwargs) row of Image model save method. I know it's because of GenericTabularInline class doesn't have a "save_model" method to override.

So, the question is, how can I override save method and set current user on InlineModelAdmin classes?

Murat Çorlu
  • 8,207
  • 5
  • 53
  • 78
  • from this document, GenericTabularInline is inherited from InlineModelAdmin http://docs.nullpobug.com/django/trunk/django.contrib.contenttypes.generic.GenericTabularInline-class.html And you can specify the form used in an InlineModelAdmin https://docs.djangoproject.com/en/dev/ref/contrib/admin/#django.contrib.admin.InlineModelAdmin.form Probably you can override the ModelForm it uses by default and add initial values? http://stackoverflow.com/questions/2988548/overriding-initial-value-in-modelform – Xun Yang Aug 21 '12 at 09:35

3 Answers3

2

I have found a solution on another question: https://stackoverflow.com/a/3569038/198062

So, I changed my BaseAdmin model class like that, and it worked like a charm:

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()

Note that, you must extend same abstract class for the ModelAdmin that contains the inlines to use this solution. Or you can add that save_formset method to ModelAdmin that contains the inline specifically.

Community
  • 1
  • 1
Murat Çorlu
  • 8,207
  • 5
  • 53
  • 78
1

I wanted the user to be set on all my models no matter where/how they were manipulated. It took me forever to figure it out, but here's how to set it on any model using middleware:

"""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
mindlace
  • 313
  • 2
  • 8
  • Having multiple requests being processed parallely, you might end-up in a race condition where `mark_whodid` is called twice with the different users. Assuming that signals are called back in the current thread, this race condition can be eliminated, by storing the request in a threadlocal, and sending it to the listener (`mark_whodid`) – Vajk Hermecz Aug 28 '13 at 12:06
1

In addition to mindlace's answer; when the created_by field happens to have null=True the not instance.created_by gives an error. I use instance.created_by_id is None to avoid this.

(I'd rather have posted this as a comment to the answer, but my current reputation doesn't allow...)

rbe
  • 19
  • 2