Override save_model on Django InlineModelAdmin

后端 未结 6 1595
星月不相逢
星月不相逢 2020-12-31 03:38

I have a model that has a user field that needs to be auto-populated from the currently logged in user. I can get it working as specified here if the user

相关标签:
6条回答
  • 2020-12-31 04:12

    Only the save_model for the model you're editing is executed, instead you will need to use the post_save signal to update inlined data.

    (Not really a duplicate, but essentially the same question is being answered in Do inline model forms emmit post_save signals? (django))

    0 讨论(0)
  • 2020-12-31 04:15

    Have you tried implementing custom validation in the admin as it is described in the documentation? Overriding the clean_user() function on the model form might do the trick for you.

    Another, more involved option comes to mind. You could override the admin template that renders the change form. Overriding the change form would allow you to build a custom template tag that passes the logged in user to a ModelForm. You could then write a custom init function on the model form that sets the User automatically. This answer provides a good example on how to do that, as does the link on b-list you reference in the question.

    0 讨论(0)
  • 2020-12-31 04:22

    I know I'm late to the party, but here's my situation and what I came up with, which might be useful to someone else in the future.

    I have 4 inline models that need the currently logged in user.

    • 2 as a created_by type field. (set once on creation)
    • and the 2 others as a closed_by type field. (only set on condition)

    I used the answer provided by rafadev and made it into a simple mixin which enables me to specify the user field name elsewhere.

    The generic formset in forms.py

    from django.forms.models import BaseInlineFormSet
    
    class SetCurrentUserFormset(forms.models.BaseInlineFormSet):
        """
        This assume you're setting the 'request' and 'user_field' properties
        before using this formset.
        """
        def save_new(self, form, commit=True):
            """
            This is called when a new instance is being created.
            """
            obj = super(SetCurrentUserFormset, self).save_new(form, commit=False)
            setattr(obj, self.user_field, self.request.user)
            if commit:
                obj.save()
            return obj
    
        def save_existing(self, form, instance, commit=True):
            """
            This is called when updating an instance.
            """
            obj = super(SetCurrentUserFormset, self).save_existing(form, instance, commit=False)
            setattr(obj, self.user_field, self.request.user)
            if commit:
                obj.save()
            return obj
    

    Mixin class in your admin.py

    class SetCurrentUserFormsetMixin(object):
        """
        Use a generic formset which populates the 'user_field' model field
        with the currently logged in user.
        """
        formset = SetCurrentUserFormset
        user_field = "user" # default user field name, override this to fit your model
    
        def get_formset(self, request, obj=None, **kwargs):
            formset = super(SetCurrentUserFormsetMixin, self).get_formset(request, obj, **kwargs)
            formset.request = request
            formset.user_field = self.user_field
            return formset
    

    How to use it

    class YourModelInline(SetCurrentUserFormsetMixin, admin.TabularInline):
        model = YourModel
        fields = ['description', 'closing_user', 'closing_date']
        readonly_fields = ('closing_user', 'closing_date')
        user_field = 'closing_user' # overriding only if necessary
    

    Be careful...

    ...as this mixin code will set the currently logged in user everytime for every user. If you need the field to be populated only on creation or on specific update, you need to deal with this in your model save method. Here are some examples:

    class UserOnlyOnCreationExampleModel(models.Model):
        # your fields
        created_by = # user field...
        comment = ...
    
        def save(self, *args, **kwargs):
            if not self.id:
                # on creation, let the user field populate
                self.date = datetime.today().date()
                super(UserOnlyOnCreationExampleModel, self).save(*args, **kwargs)
            else:
                # on update, remove the user field from the list
                super(UserOnlyOnCreationExampleModel, self).save(update_fields=['comment',], *args, **kwargs)
    

    Or if you only need the user if a particular field is set (like boolean field closed) :

    def save(self, *args, **kwargs):
    
        if self.closed and self.closing_date is None:
            self.closing_date = datetime.today().date()
            # let the closing_user field set
        elif not self.closed :
            self.closing_date = None
            self.closing_user = None # unset it otherwise
    
        super(YourOtherModel, self).save(*args, **kwargs)  # Call the "real" save() method.
    

    This code could probably be made way more generic as I'm fairly new to python but that's what will be in my project for now.

    0 讨论(0)
  • 2020-12-31 04:29

    Here's what I think is the best solution. Took me a while to find it... this answer gave me the clues: https://stackoverflow.com/a/24462173/2453104

    On your admin.py:

    class YourInline(admin.TabularInline):
        model = YourInlineModel
        formset = YourInlineFormset
    
        def get_formset(self, request, obj=None, **kwargs):
            formset = super(YourInline, self).get_formset(request, obj, **kwargs)
            formset.request = request
            return formset
    

    On your forms.py:

    class YourInlineFormset(forms.models.BaseInlineFormSet):
        def save_new(self, form, commit=True):
            obj = super(YourInlineFormset, self).save_new(form, commit=False)
            # here you can add anything you need from the request
            obj.user = self.request.user
    
            if commit:
                obj.save()
    
            return obj
    
    0 讨论(0)
  • 2020-12-31 04:34

    I had a similar issue with a user field I was trying to populate in an inline model. In my case, the parent model also had the user field defined so I overrode save on the child model as follows:

    class inline_model:
        parent = models.ForeignKey(parent_model)
        modified_by = models.ForeignKey(User,editable=False) 
        def save(self,*args,**kwargs):
            self.modified_by = self.parent.modified_by
            super(inline_model,self).save(*args,**kwargs)
    

    The user field was originally auto-populated on the parent model by overriding save_model in the ModelAdmin for the parent model and assigning

    obj.modified_by = request.user
    

    Keep in mind that if you also have a stand-alone admin page for the child model you will need some other mechanism to keep the parent and child modified_by fields in sync (e.g. you could override save_model on the child ModelAdmin and update/save the modified_by field on the parent before calling save on the child).

    I haven't found a good way to handle this if the user is not in the parent model. I don't know how to retrieve request.user using signals (e.g. post_save), but maybe someone else can give more detail on this.

    0 讨论(0)
  • 2020-12-31 04:38

    Does the other model save the user? In that case you could use the post_save signal to add that information to the set of the inlined model.

    0 讨论(0)
提交回复
热议问题