Opened 4 years ago

Closed 4 years ago

Last modified 4 years ago

#14969 closed (wontfix)

To have a way to modify third part model classes

Reported by: marinho Owned by: nobody
Component: Database layer (models, ORM) Version: master
Severity: Keywords:
Cc: Triage Stage: Unreviewed
Has patch: no Needs documentation: no
Needs tests: no Patch needs improvement: no
Easy pickings: UI/UX:

Description

Sometimes we need to modify a model class from a third part application or a Django's contrib, just for a specific project. The most common situations are related to the User, Group and Permission classes, like add new fields, or change unicode, etc.

So I wrote this metaclass to allow us to modify a model class from another place of code without really change the original code.

Basically you just inherit ModifiedModel, set a Meta inner class with model = 'application.Model' and set attributes with model fields, like below:

"""

from django.db import models
from django.contrib.auth.models import User

from modify_models import ModifiedModel

class ModifyingUser(ModifiedModel):

class Meta:

model = User
exclude = ('first_name','last_name',)

website = models.URLField(blank=True, verify_exists=False)

def unicode(self):

return '%s - %s'%(self.pk, self.name)

"""

The code above adds a new field "website", excludes "first_name" and "last_name" and replaces the descriptor unicode in the User model.

In my projects and some of my clients sometimes we need this, so I thought it can be useful for other people and could be part of Django.

I'm attaching a simple Python file to this ticket.

If/when the ticket be approved I can work on documentations and tests and maybe improve the code to be part of the framework (i.e. re-think names).

Attachments (1)

modify_models.py (3.9 KB) - added by marinho 4 years ago.

Download all attachments as: .zip

Change History (7)

Changed 4 years ago by marinho

comment:1 Changed 4 years ago by marinho

  • Needs documentation unset
  • Needs tests unset
  • Patch needs improvement unset

Fixing example code:

from django.db import models
from django.contrib.auth.models import User

from modify_models import ModifiedModel

class ModifyingUser(ModifiedModel):
    class Meta:
        model = User
        exclude = ('first_name','last_name',)

    website = models.URLField(blank=True, verify_exists=False)

    def __unicode__(self):
        return '%s - %s'%(self.pk, self.name)

comment:2 follow-up: Changed 4 years ago by russellm

  • Resolution set to wontfix
  • Status changed from new to closed

Erm... No. Monkeypatching is bad, mmmkay?

Comments gives you the *right* way to handle this problem -- you define an interface, and make the model itself pluggable. Not all Django's contrib apps follow this approach, but that doesn't mean we bake monkeypatching into the core -- we fix the contrib apps.

comment:3 in reply to: ↑ 2 ; follow-up: Changed 4 years ago by marinho

  • Resolution wontfix deleted
  • Status changed from closed to reopened

Replying to russellm:

Russel, I disagree to you. It would be a monkey patch if it was just a work around to fix something and just that.

I reopen the ticket just to ask for your attention, but I will give up if you close it again.

So, my code is more to an implementation of pattern Helper Classes than to a monkey patch.

Let's think on django-tagging, just for illustration. It is a good application, but let's consider that for a specific project I'd like to add a new field for model class Tag: "icon" (an ImageField).

Well, with actual state, I should to do one of the following:

  1. ask the author to add the field (of course would be insane to ask him to add a field just because I need for a specific project);
  2. fork it as my own my-project-django-tagging and modify it. Well, this would be the opposite of "DRY";

But let's consider that the app's author made your "right" way, so I would use something like the setting AUTH_PROFILE_MODULE, so, let's name it "TAGGING_TAG_PROFILE" to add a field.

Well, this could happen with any model class of any application (contribs, pluggable, public, privates, etc.), so to implement the "right" way as you said, would be to have a setting for each model class, so, IMO would be practically impossible or a totally mess.

But, let's consider I would create a model class "TagProfile" and attach to that setting, so I would have a new model class with one field. So every code I write using tags I have to use a "my_tag.get_profile().icon" so we have the double of database requests for just one field.

Another consequence is that I have two Admin modules: one for tagging.Tag and another one for my TagProfile (I could change admin.site._registry[Tag] but would be a classic monkey patch).

And we must consider many times would be complicated to write aggregations, to use tag clouds, or even to use the applications features and going on...

So, my ticket is about to implement a way to make single changes on third part (or contrib) applications without to make hard code.

Actually, IMO, User profiles never was a great solution, because it makes me to do the double of database requests and to have two places in Admin for the same reason, sometimes just to have a field like "website", so this is not really smart, actually users never understand why to change "website" is different to change "email". I use it because is a better way than inherit or make my own, but not really happy :)

comment:4 in reply to: ↑ 3 ; follow-up: Changed 4 years ago by lrekucki

  • Resolution set to wontfix
  • Status changed from reopened to closed

Note that Russell didn't said that contrib.auth does it the right way. Take a look at how contrib.comments - it's not perfect, but far better. As for your tag example, you should take a look at django-taggit - it handles custom Tag models pretty well without monkey patching.

From a brief review of your code, this doesn't need any change in Django to work, so you could just make a 3rd party application out if it.

PS. Writing in bold doesn't help convincing other people.

comment:5 in reply to: ↑ 4 Changed 4 years ago by marinho

Replying to lrekucki:

Note that Russell didn't said that contrib.auth does it the right way. Take a look at how contrib.comments - it's not perfect, but far better. As for your tag example, you should take a look at django-taggit - it handles custom Tag models pretty well without monkey patching.

I disagree, but as I said, I just give up.

From a brief review of your code, this doesn't need any change in Django to work, so you could just make a 3rd party application out if it.

Yes, it is already part of django-plus.

PS. Writing in bold doesn't help convincing other people.

Pointless ;)

comment:6 Changed 4 years ago by schinckel

If you really feel the need to MonkeyPatch (and sometimes, it is unavoidable), then just use Model.add_to_class.

You can even do this to modify contrib apps' models (for instance, I use it to add a field to Group, so I can have a hierarchy of groups).

Just make sure you are very careful with your patching (ie, check to see if the field/attribute/method already exists on the model class, for instance). And be aware that you may break things.

Note: See TracTickets for help on using tickets.
Back to Top