How to limit fields in django-admin depending on user?

后端 未结 4 1431
無奈伤痛
無奈伤痛 2020-12-24 12:56

I suppose similar problem would have been discussed here, but I couldn\'t find it.

Let\'s suppose I have an Editor and a Supervisor. I want the Editor to be able to

4条回答
  •  被撕碎了的回忆
    2020-12-24 13:15

    I have a system kind of like this on a project that I'm just finishing up. There will be a lot of work to put this together, but here are some of the components that I had to make my system work:

    • You need a way to define an Editor and a Supervisor. The three ways this could be done are 1.) by having an M2M field that defines the Supervisor [and assuming that everyone else with permission to read/write is an Editor], 2.) make 2 new User models that inherit from User [probably more work than necessary] or 3.) use the django.auth ability to have a UserProfile class. Method #1 is probably the most reasonable.

    • Once you can identify what type the user is, you need a way to generically enforce the authorization you're looking for. I think the best route here is probably a generic admin model.

    • Lastly you'll need some type of "parent" model that will hold the permissions for whatever needs to be moderated. For example, if you had a Blog model and BlogPost model (assuming multiple blogs within the same site), then Blog is the parent model (it can hold the permissions of who approves what). However, if you have a single blog and there is no parent model for BlogPost, we'll need some place to store the permissions. I've found the ContentType works out well here.

    Here's some ideas in code (untested and more conceptual than actual).

    Make a new app called 'moderated' which will hold our generic stuff.

    moderated.models.py

    class ModeratedModelParent(models.Model):
        """Class to govern rules for a given model"""
        content_type = models.OneToOneField(ContentType)
        can_approve = models.ManyToManyField(User)
    
    class ModeratedModel(models.Model):
        """Class to implement a model that is moderated by a supervisor"""
        is_approved = models.BooleanField(default=False)
    
        def get_parent_instance(self):
            """
            If the model already has a parent, override to return the parent's type
            For example, for a BlogPost model it could return self.parent_blog
            """
    
            # Get self's ContentType then return ModeratedModelParent for that type
            self_content_type = ContentType.objects.get_for_model(self)
            try:            
                return ModeratedModelParent.objects.get(content_type=self_content_type)
            except:
                # Create it if it doesn't already exist...
                return ModeratedModelParent.objects.create(content_type=self_content_type).save()
    
        class Meta:
            abstract = True
    

    So now we should have a generic, re-usable bit of code that we can identify the permission for a given model (which we'll identify the model by it's Content Type).

    Next, we can implement our policies in the admin, again through a generic model:

    moderated.admin.py

    class ModeratedModelAdmin(admin.ModelAdmin):
    
        # Save our request object for later
        def __call__(self, request, url):
            self.request = request
            return super(ModeratedModelAdmin, self).__call__(request, url)
    
        # Adjust our 'is_approved' widget based on the parent permissions
        def formfield_for_dbfield(self, db_field, **kwargs):
            if db_field.name == 'is_approved':
                if not self.request.user in self.get_parent_instance().can_approve.all():
                    kwargs['widget'] = forms.CheckboxInput(attrs={ 'disabled':'disabled' })
    
        # Enforce our "unapproved" policy on saves
        def save_model(self, *args, **kwargs):
            if not self.request.user in self.get_parent_instance().can_approve.all():
                self.is_approved = False
            return super(ModeratedModelAdmin, self).save_model(*args, **kwargs)
    

    Once these are setup and working, we can re-use them across many models as I've found once you add structured permissions for something like this, you easily want it for many other things.

    Say for instance you have a news model, you would simply need to make it inherit off of the model we just made and you're good.

    # in your app's models.py
    class NewsItem(ModeratedModel):
        title = models.CharField(max_length=200)
        text = models.TextField()
    
    
    # in your app's admin.py
    class NewsItemAdmin(ModeratedModelAdmin):
        pass
    
    admin.site.register(NewsItem, NewsItemAdmin)
    

    I'm sure I made some code errors and mistakes in there, but hopefully this can give you some ideas to act as a launching pad for whatever you decide to implement.

    The last thing you have to do, which I'll leave up to you, is to implement filtering for the is_approved items. (ie. you don't want un-approved items being listed on the news section, right?)

提交回复
热议问题