0

I've got a large library of Django apps that are shared by a handful of Django projects/sites. Within each project/site there is an option to define a 'Mix In' class that will be mixed in to one of the in-library base classes (which many models sub-class from).

For this example let's say the in-library base class is PermalinkBase and the mix-in class is ProjectPermalinkBaseMixIn.

Because so many models subclass from PermalinkBase, not all the methods/properities defined in ProjectPermalinkBaseMixIn will be utilitized by all of PermalinkBase's subclasses.

I'd like to write a decorator that can be applied to methods/properties within ProjectPermalinkBaseMixIn in order to limit them from running (or at least returning None) if they are accessed from a non-approved class.

Here's how I'm doing it now:

class ProjectPermalinkBaseMixIn(object):
    """
    Project-specific Mix-In Class to `apps.base.models.PermalinkBase`
    """

    def is_video_in_season(self, season):
        # Ensure this only runs if it is being called from the video model
        if self.__class__.__name__ != 'Video':
            to_return = None
        else:
            videos_in_season = season.videos_in_this_season.all()
            if self in list(videos_in_season):
                to_return = True
            else:
                to_return False

        return to_return

Here's how I'd like to do it:

class ProjectPermalinkBaseMixIn(object):
    """
    Project-specific Mix-In Class to `apps.base.models.PermalinkBase`
    """

    @limit_to_model('Video')
    def is_video_in_season(self, season):
        videos_in_season = season.videos_in_this_season.all()
        if self in list(videos_in_season):
            to_return = True
        else:
            to_return = False

        return to_return

Is this possible with decorators? This answer helped me to better understand decorators but I couldn't figure out how to modify it to solve the problem I listed above.

Are decorators the right tool for this job? If so, how would I write the limit_to_model decorator function? If not, what would be the best way to approach this problem?

Community
  • 1
  • 1
respondcreate
  • 1,780
  • 1
  • 20
  • 23
  • 3
    Why do you want to do this?! – ThiefMaster Dec 23 '12 at 15:33
  • 1
    I think you have massively overcomplicated your design. A mix-in that only works on certain classes isn't a mix-in. Just make an abstract base class and then define that functionality in the class itself. (Presuming that it's shared functionality, in this case, it looks like it just needs to be a function on the given class). – Gareth Latty Dec 23 '12 at 15:35
  • Also, what's with the Yoda logic? You check if it isn't true, then do what you want in the `else` case. – Gareth Latty Dec 23 '12 at 15:36
  • @ThiefMaster The `Video` model above is used by 8 different projects but only one has the 'season' functionality. I don't want to cruft up the other projects by adding in that functionality which is why I'm adding that method to the project-specific mix-in. – respondcreate Dec 23 '12 at 15:42
  • Ok... but the method being there in the mixin doesn't hurt and adding such a check just makes thing slower and uglier. Oh, and please don't use `if foo: return True: else: return False`. Just `return foo` or `return bool(foo)` if necessary in such a case! – ThiefMaster Dec 23 '12 at 15:42
  • @Lattyware I'm not worried about it working (it works fine as it is). I'm just curious if there's a way to use decorators to accomplish this particular task. – respondcreate Dec 23 '12 at 15:45
  • @respondcreate I'm not saying it won't work, just that it's a massively overcomplicated design. Why check if it's being used by a particular class? Tell people that's what it's for, but why try to force them to only use it in that way? Hell, why does it matter that the class has functionality that some things don't use? I don't rip the standard library out of Python, but I definitely don't use it all in every program. – Gareth Latty Dec 23 '12 at 15:53
  • And even then, checking the class name makes much less sense than `isinstance`, unless you really mean to make sure the single class, not any subclass of it, can use this mixin. – Silas Ray Dec 23 '12 at 16:08
  • Thanks for the feedback, everyone! You all made some great points about over-complication...I think I was trying to do too much unnecessary 'magic'. That being said, is there a way for decorators to be able to act on an in-class method's 'self' or can they only ever affect its net 'return' value? – respondcreate Dec 23 '12 at 17:30
  • @respondcreate Hi, I think to solve your issue you should try to find a different design to overcome this issue. However, one question, will that method be called externally or internally by one of the mixins sub-classes? – andrefsp Dec 23 '12 at 18:22
  • @andrefsp: Agreed. A different design is definitely needed for this particular issue. That being said, the method would be called internally by one of the mix-ins subclasses. – respondcreate Dec 23 '12 at 18:24
  • @respondcreate Ok, I will write an answer. – andrefsp Dec 23 '12 at 18:53

1 Answers1

1

was looking at your problem and I think this might be an overcomplicated way to achieve what you are trying to do. However I wrote this bit of code:

def disallow_class(*klass_names):
    def function_handler(fn):
        def decorated(self, *args, **kwargs):
            if self.__class__.__name__ in klass_names:
                print "access denied to class: %s" % self.__class__.__name__
                return None
            return fn(self, *args, **kwargs)
        return decorated
    return function_handler


class MainClass(object):

    @disallow_class('DisallowedClass', 'AnotherDisallowedClass')
    def my_method(self, *args, **kwargs):
        print "my_method running!! %s" % self


class DisallowedClass(MainClass): pass

class AnotherDisallowedClass(MainClass): pass

class AllowedClass(MainClass): pass


if __name__ == "__main__":
    x = DisallowedClass()
    y = AnotherDisallowedClass()
    z = AllowedClass()
    x.my_method()
    y.my_method()
    z.my_method()

If you run this bit of code on your command line the output will be something like:

access denied to class: DisallowedClass
access denied to class: AnotherDisallowedClass
my_method running!! <__main__.AllowedClass object at 0x7f2b7105ad50>

Regards

andrefsp
  • 3,580
  • 2
  • 26
  • 30