extending get_user_model

Hello,

I’ve added in custom fields for users by following the AbstractBaseUser tutorials which is all working fine. I was then thinking that when I call User=get_user_model() I would then see the custom fields as a property/attribute. Please could someone point me in the right direction on how to obtain all the field values for the currently logged in user? I actually want to add a way to indicate if the user could have access to data based on custom logic i.e. if the user has this certain custom field attribute then they can edit data, else no the can’t (they can be logged in, just can’t do certain actions). If there is a better way for me to achieve this then I’m all ears.

Thank you

So the appropriate way to handle this would be to use Django’s Group and Permission system.

You would create a named Permission to indicate the different actions that can be performed (or use the automatically created permissions if they exist), assign those permissions to one or more groups, then make the users members of the appropriate groups. You can then test for the user having those permissions in each view where it is necessary to check.

See the docs at Using the Django authentication system | Django documentation | Django for all the details.

Thank you for your reply. I’ll provide a brief explaination of what I am doing as I’m not sure permissions is quite right but then again, I may be going about this the wrong way. So, within my user model, I have a manytomany link to a list of organisations. I can then assign a user to one or more organisations. Anyone using the website can select an organisation and below it be presented with a list of events that are on however, if the user is logged in and has that specific organisation assigned to them, then they can see an edit button for example. This feels like something that must be done all the time but I can’t atm see the best approach. It doesn’t feel right putting the same code for ‘is the user logged in and are they assigned to this organisation to which the event belong’ all over the place so I’m sure there is a much neater way to do this so that within any view or template I can check if the person can edit events and also see edit related buttons.

If I understand you correctly, it’s not that you want to prevent access to the view, but that the view renders differently based upon organizational assignments.

But that’s exactly what you’re trying to do.

You aren’t going to be able to get around performing these tests everywhere, but that does not mean that the code to perform the test needs to exist in all those places. You can create a model method that performs the test, then check that method everyplace you need to use it.

By default, Django defines the use of the has_perm method for this purpose.

That method (which you override in your User model) can be passed a specific object being acted upon, allowing it to check characteristics of that model.

Thank you, that has put me on the right road. I think this is what I need to follow Custom Permission Classes in Django REST Framework | TestDriven.io

I’m sure that has some useful information in there, but I don’t see where it covers your use-case of altering the response based upon the object being requested.

I was thinking that has_object_permission would be where I would put the logic to check user can access certain events based on whether they are for an organisation to which the user belongs?

Django does not define a method named “has_object_permission”.

sorry you are right - I’m not a very experienced programmer so still trying to get my head around object programming. OK, so if I understand correctly, I should create a new permission like “in_organisation” on the custom user module and then use has_perm(“in_organisation”) to test for each user but I still don’t understand how/where I programmtically set whether the user has “in_organisation” permission or not?

No, I’m not suggesting that at all.

In your custom User model, define a function named has_perm, with the signature as defined in the docs. (The method signature includes passing an object as a parameter.) This has_perm method has the logic to determine whether that user shares an organization with the object that was passed to it, and should return a True or False response.

In your views, every view that needs to make this determination should call this method and save the result in a variable. You can then test that variable anywhere else in the view to verify what that user can do with that object. You can also pass that variable through to the template(s) being rendered by including it in the context.

In this situation, you do not need to use the actual Permission class.

This seems to work :

In CustomUser class I added this :

      def has_perm(perm, obj):

        if perm == 'check_in_org':
            User = get_user_model()
            user_orgs = User.objects.first().asc_access.all()

            for org in user_orgs:

                if obj.id == org.id:
                    return True

            return False

        return super().has_perm(perm, obj)

and in views.py I changed my events view to:

def events(request, association_id):

    org = Association.objects.filter(pk=association_id).first()

    events_list = Event.objects.filter(
        association=association_id, active=True, when__gt=event_past_date).order_by("when")

    add_edit_event = False

    can_manage_org = CustomUser.has_perm('check_in_org', org)

    if can_manage_org:
        add_edit_event = True

    context = {
        "active_events_list": events_list,
        "can_add_edit": add_edit_event,
    }
    return render(request, "showorderofplay/events.html", context)

Basically I pass the Association (Orgainisation) object to has_perm to compare to the user asc_access (orgainisation access). I had to create a ‘permission’ to filter this check out as it initially broke admin.

I changed the has_perm method to always allow superuser access and no access to logged out (anonymous) users. I have one browser that is logged in as super user and one not logged in at all. The one that is not logged in thinks the person is logged in (so not anonymous) and they are super user! What am I doing wrong?

   def has_perm(perm, obj):
        User = get_user_model()

        if perm == 'check_in_association':
            if User.objects.first().is_anonymous:
                print("anon")
                return False
            elif User.objects.first().is_superuser:
                print ("super")
                return True

            else:
                user_associations = User.objects.first().asc_access.all()

                for asc in user_associations:

                    if obj.id == asc.id:
                        return True

                return False

        return super().has_perm(perm, obj)

So even though Firefox is not logged in, if I print ( User.objects.first().email) it comes out as a superuser (me) rather than anonymous. I’m so confused.

I think this fixes it, I created my own ‘has perm’ method:

models.py for CustomUser class

    def my_has_perm(perm, obj, request):

        if perm == 'check_in_association':
            if request.user.is_anonymous:
                print("anon")
                return False
            elif request.user.is_superuser:
                print("super")
                return True

            else:
                user_associations = CustomUser.objects.filter(
                    pk=request.user.id).first().asc_access.all()
                for asc in user_associations:
                    if obj.id == asc.id:
                        return True

                return False

        return True

view.py

def events(request, association_id):

    association = Association.objects.filter(pk=association_id).first()

    events_list = Event.objects.filter(
        association=association_id, active=True, when__gt=event_past_date).order_by("when")

    context = {
        "active_events_list": events_list,
        "can_add_edit": CustomUser.my_has_perm('check_in_association', association, request),
    }
    return render(request, "showorderofplay/events.html", context)

There are a couple things here that you are missing.

First, you should spend some time reading 9. Classes — Python 3.12.2 documentation.

In your view you have:

This ends up calling this method, but on the class, not the individual user making the request. Doing it this way is what’s making you pass the request as an addtional parameter when it’s not necessary to do so. (Notice that the official signature for it does not make a provision for doing this.)

You should be calling this function on the user making the request:
request.user.has_perm(...

This changes how you need to define has_perm in the model.

A class method, when called on an instance of that class, always passes the instance of itself as the first parameter. By convention, this first parameter is named self.

That means your function definition should look like this:
def has_perm(self, perm, obj=None)

That allows you to reference the attributes of the current user in that method as self.whatever. In the case of the properties is_anonymous and is_superuser, your tests become if self.is_anonymous: and if self.is_superuser. There’s no need for you to issue another query to retrieve the user, because self is the user.

I will take the time to read the python classes description as I admit I don’t fully get how classes work at the moment. I have corrected my code and it does indeed look much nicer now that I have made use of self. Part of the challenge is I’m not always sure what can be called from what.

def has_perm(self, perm, obj=None):
        if perm == 'check_in_association':
            if self.is_anonymous:
                print("anon")
                return False
            elif self.is_superuser:
                print("super")
                return True
            else:
                print("checking")
                user_associations = self.asc_access.all()
                for asc in user_associations:
                    if obj.id == asc.id:
                        return True

                return False

        return True```

This:

Can be simplified by using a filter and the exists function.

if self.asc_access.filter(id=obj.id).exists():
    return True
return False

Or:

return self.asc_access.filter(id=obj.id).exists()