Opened 17 years ago

Closed 12 years ago

Last modified 10 years ago

#3148 closed New feature (wontfix)

Add getters and setters to model fields

Reported by: jerf@… Owned by:
Component: Database layer (models, ORM) Version: dev
Severity: Normal Keywords:
Cc: Taiping, jerf@…, gav@…, terrex2, ramusus@…, semente+djangoproject@…, erikrose, Chris Chambers, denilsonsa@…, Christopher Grebs, noah, inactivist@… Triage Stage: Accepted
Has patch: yes Needs documentation: yes
Needs tests: no Patch needs improvement: yes
Easy pickings: no UI/UX: no

Description

Whenever you have two distinct ways to update a variable, you introduce the opportunity to have bugs. This becomes increasingly true as the system grows in size.

It is often the case that when changing one field on an object, you want to be able to automatically run code of some kind; this is the same basic motivation behind the "property" builtin in Python. However, the obvious way of doing that in Django doesn't work:

class Something(models.Model):
    field = models.BooleanField(...)

    ...

    def set_field(self, value):
        # do something

    field = property(set_field)

The second field overrides the first, and in the process of constructing the model Django never gets a chance to see the models.BooleanField.

This patch adds a 'getter' and 'setter' attribute to all fields, which takes a string of a method to call when a field is retrieved or set. It turns out that it is fairly easy to add a property to the class during Django's initialization, at which point it has already retrieved the field information. This example from the enclosed tests shows the basics of its usage:

class GetSet(models.Model):
    has_getter = models.CharField(maxlength=20, getter='simple_getter')
    has_setter = models.CharField(maxlength=20, setter='simple_setter')
    has_both = models.CharField(maxlength=20, getter='simple_getter',
                                setter='updater')
    updated_length_field = models.IntegerField(default=0)

    def simple_getter(self, value):
        return value + "_getter"
    def simple_setter(self, value):
        return value + "_setter"
    def updater(self, value):
        self.updated_length_field = len(value)
        return value

This defines a getter on has_getter that returns a filtered value from the DB, a setter on has_setter that processes the value to add "_setter" to it in all cases, and on has_both we see a setter than implements the use case of updating another field when a property is set. (As is often the case, this is a trivial example; in my real code, for instance, the value being updated is actually in a related object.)

A getter receives as its argument the current "real" value (the one that either came from the database, object construction, or a prior setting of the value), and what the getter returns is actually what the user gets back from the attribute.

A setter receives as its argument the value that the user is setting the property to, and what it returns is what the property will actually be set to. The pattern for just using that as a hook is to return what is passed in after you've done whatever it is your hook does, as shown above.

These properties are only created for fields that have getters or setters, so the backwards-compatibility impact of this should be zero, and the performance impact should be a check for getters/setters per field once at startup, which is minimal. I'm a little less certain about exactly how these getters and setters will interact with all the various field types, but due to the way in which it hooks in it should, in theory, have minimal impact, because no Python code should fail to go through the property.

Getters and setters do not operate during the creation of the object, be it by retrieval from the database or creation by instantiating the class; this avoids a lot of tricky issues with property initialization order and double-application of some common setters, but will need to be documented.

I'd be happy to add the appropriate documentation but I do not see where to contribute that.

Attachments (14)

add.getter.setter.patch (5.0 KB ) - added by jerf@… 17 years ago.
patch to add getters and setters
revised.add.getter.setter.patch (6.2 KB ) - added by jerf@… 17 years ago.
add documentation, fix minor whitespace issue, supercede previous patch
revised2.add.getter.setter.patch (7.0 KB ) - added by jerf@… 17 years ago.
figured out what the numbers in the test cases meant; documentation example added for getter/setter
3148.diff (3.7 KB ) - added by Marc Fargas 16 years ago.
New patch using python's property(), includes docs and tests
3148.2.diff (3.6 KB ) - added by Marc Fargas 16 years ago.
Updated diff according to comments.
3148.3.diff (3.6 KB ) - added by Marc Fargas 16 years ago.
Fixed small typo in the docs patch.
3148.4.diff (3.6 KB ) - added by Marc Fargas 16 years ago.
Updated patch (merge conflict resolved) to latest trunk.
3148.5.diff (3.4 KB ) - added by terrex2 16 years ago.
updated patch for rev.8463
django-modelfield-getter-setters.diff (3.5 KB ) - added by Daniel Hahler 14 years ago.
Patch against trunk r11791 - new approach
django-3148.diff (3.8 KB ) - added by Daniel Hahler 14 years ago.
Slightly updated patch
django-3148-approach-via-descriptors.diff (5.4 KB ) - added by Daniel Hahler 14 years ago.
Approach using FieldDescriptor class (and using decorators to decide if fields get wrapped into it or not)
3148-propertyfield.diff (4.9 KB ) - added by berdario 13 years ago.
This approach works with m2m fields and inherited fields
3148-propertyfield2.diff (5.9 KB ) - added by berdario 13 years ago.
slightly more complex approach, that on the other hand manages to obtain the same behaviour with m2m fields
3148-propertyfield3.diff (6.6 KB ) - added by berdario 13 years ago.
Fixes to the previous patch

Download all attachments as: .zip

Change History (65)

by jerf@…, 17 years ago

Attachment: add.getter.setter.patch added

patch to add getters and setters

by jerf@…, 17 years ago

add documentation, fix minor whitespace issue, supercede previous patch

comment:1 by Adrian Holovaty, 17 years ago

Before checking in this new functionality, I'd be very interested in seeing whether it would be possible to allow for "normal" property usage on models, rather than inventing a new way of doing it.

Seems like it would be tricky to figure it out, but that shouldn't stop us from experimenting... Who wants to try?

by jerf@…, 17 years ago

figured out what the numbers in the test cases meant; documentation example added for getter/setter

comment:2 by anonymous, 17 years ago

Cc: jerf@… added

Explained why I don't think there is a better way in this post.

comment:3 by Simon G. <dev@…>, 17 years ago

Triage Stage: UnreviewedDesign decision needed

comment:4 by enrobberors, 17 years ago

Cc: Taiping added; jerf@… removed
Keywords: MESSAGE added

If you've got a problem chum, think how it could be.

comment:5 by enrobberors, 17 years ago

If you've got a problem chum, think how it could be.

comment:6 by anonymous, 17 years ago

Cc: jerf@… added

comment:7 by Wintermute, 16 years ago

Hi,

Has this issue been forgotten? I think there must be quite many developers who wish to do processing based on field changes. At least I do and this patch would provide the solution.

by Marc Fargas, 16 years ago

Attachment: 3148.diff added

New patch using python's property(), includes docs and tests

comment:8 by Marc Fargas, 16 years ago

Owner: changed from nobody to Marc Fargas
Status: newassigned
Summary: [patch] Add getters and setters to model fieldsAdd getters and setters to model fields
Version: SVN

Hi there,
I found that ticket looking for a way to achieve exactly what this ticket asks for and saw adrian's comment above about trying to use normal properties to improve the patch.

I've just attached a new patch with tests and docs that does just this. In essence you'd do:

    from django.db import models
    class Person(models.Model):
        def _get_name(self):
            return self.__name
        def _set_name(self, value):
            self.__name = value

        name = models.CharField(max_length=30, property=(_get_name, _set_name))

And django will create "Person.name" as a property() using the tuple given.

comment:9 by Marc Fargas, 16 years ago

Triage Stage: Design decision neededAccepted

Moving to Accepted because:

  • The only "core" request was in comment 3 (from adrian) about doing this "more pythonic" and it is about "doing it better" not "I'm not sure if it fits well in Django".
  • There's nothing in the comments that imply "Design Decision", meaning that what the tickets requests/does does not seem to imply this status.
  • Trying to give a bit of visibility to the ticket (Reasons 1 & 2 are only excuses! ;))

comment:10 by Chris Beaven, 16 years ago

Reviewing your patch:

  1. Using the reserved word "property" sounds like it'll get you in to trouble. In fact, I can't see how the patch would even work since you try to setattr to property(getter, setter, delter, docter)) but property would be defined as the tuple! How about using property_ or use_property or something.
  1. why do you need to keep the property tuple around? (i.e. self.property = property)
  1. instead of your add magic, how about something simpler, like:
    if len(use_property) < 2
        # getter and setter are both required.
        raise ValueError("You must specify at least a getter and a setter method with use_property") 
    # Create a property on ``cls`` with the methods given. 
    setattr(cls, self.name, property(*use_property)) 
    

in reply to:  10 ; comment:11 by Marc Fargas, 16 years ago

Keywords: MESSAGE removed

Replying to SmileyChris:

Before anything, if you look at the attached patch from TRAC it might seem that everything is on the same function (Field.__init__(),
but that's not the case, there are two functions patched here: Field.__init__() and Field.contribute_to_class(). (See the line marked with dots in TRAC's diff viewer).

  1. Using the reserved word "property" sounds like it'll get you in to trouble. In fact, I can't see how the patch would even work since you try to setattr to property(getter, setter, delter, docter)) but property would be defined as the tuple! How about using property_ or use_property or something.

Well, there are tests included with the patch and they don't fail! ;)
Anyway; It does not crash because the callable property() is not used in Field.__init__() for anything. So it's safe to use it there.
Then we save the parameter in Field.property which can never be accesed as property outside of Field.__init__(), it will always be self.property.

The reason I used property= instead of something else was to make it clear what the parameter is about, and give an idea of how it works (just exactly as property())

  1. why do you need to keep the property tuple around? (i.e. self.property = property)

Because property is read in Field.__init__() and used then in Field.contribute_to_class() to create the property in the model. So I need to save the property parameter from init() to use it in contribute_to_class(). Also I wanted to be consistent, the first Thing Field.__init__() does is save all the kwargs on self. Anyway, it's needed for contribute_to_class.

  1. instead of your add magic, how about something simpler, like:
    if len(use_property) < 2
        # getter and setter are both required.
        raise ValueError("You must specify at least a getter and a setter method with use_property") 
    # Create a property on ``cls`` with the methods given. 
    setattr(cls, self.name, property(*use_property)) 
    

Uhm.. really nice idea, I'll update the patch later with this change.
Before updating, do you really thing the parameter to Field should not be property? As explained aboved, it will never cause a namespace collision until somebody wants to use property() in Field.__init__() is is unlikely to happen due to what Field.__init__() does.

in reply to:  11 comment:12 by Chris Beaven, 16 years ago

Replying to telenieko:

Before anything, if you look at the attached patch from TRAC it might seem that everything is on the same function (Field.__init__(),
but that's not the case, there are two functions patched here: Field.__init__() and Field.contribute_to_class(). (See the line marked with dots in TRAC's diff viewer).

Oops, rather big oversight on my part :P I should probably apply patches rather than just reviewing them from trac's visual diff...
So I guess you can ignore all of points 2 and 3.

Uhm.. really nice idea, I'll update the patch later with this change.
Before updating, do you really thing the parameter to Field should not be property?

Nah, I'm cool with it then - for example, Models have an id property so the precedence has been set :)

comment:13 by Malcolm Tredinnick, 16 years ago

Change the "property" name, please. There are lots of words in the English language; no need to court an accidental collision by using effectively reserved names.

by Marc Fargas, 16 years ago

Attachment: 3148.2.diff added

Updated diff according to comments.

comment:14 by Marc Fargas, 16 years ago

Here it is,
The option is now: use_property; Inside the Field class it is saved as self.model_property so it's clear what it stands for.

I also removed the add magic as per SmileyChris comment, and used his * thing, nice :)

Hope it makes you all happy!! ;)
Tests passed fine :)

by Marc Fargas, 16 years ago

Attachment: 3148.3.diff added

Fixed small typo in the docs patch.

in reply to:  description ; comment:15 by Adam, 16 years ago

Any update on this?

by Marc Fargas, 16 years ago

Attachment: 3148.4.diff added

Updated patch (merge conflict resolved) to latest trunk.

in reply to:  15 comment:16 by Marc Fargas, 16 years ago

Replying to Adam:

Any update on this?

I always keep an updated patch on my laptop but don't upload it too often.

Hope the updated patch works a bit longer than the last one! hhehe.

comment:17 by anonymous, 16 years ago

Triage Stage: AcceptedReady for checkin

comment:18 by anonymous, 16 years ago

Cc: gav@… added

comment:19 by Marty Alchin, 16 years ago

Doing some quick looking at this (I'll admit, I haven't applied it yet), I came up with a 7-line utility function that handles everything the patch does, but without touching core at all. If there's interest, I can post it at djangosnippets, but I think there's something else to consider.

While the latest patch is a considerable cleanup from the original patch, I think one of the original author's considerations has been lost in translation. Again, I haven't applied it, but it looks like it does exactly what my little utility function does, and mine certainly suffers from the double-setter problem he describes.

Malcolm's [source:django/trunk/django/db/models/fields/subclassing.py field subclassing work] does a good job of avoiding this, but it uses a descriptor, which would interfere with the property, unless a new descriptor is made that takes the double-setter issue *and* the property stuff into account. Ultimately, I think it's a matter of providing the simple, common case, like Malcolm did, and expecting those who need more to know what they need. I don't know if that's enough for everybody, but that's why I was working on a non-core option for this. I'd rather people do a little hunting on the problem they have before they just pick a "solution" that happens to be in core and get confused why it doesn't work the way it should.

Of course, one other thing SubfieldBase takes into account is serialization. Again, I haven't applied the patch or tested it out (I'm heck-deep in other stuff at the moment), but I'm guessing there are going to be some surprises, since I've run into a few things in the past when an object's __dict__ didn't line up with what some properties were advertising. I'd certainly at least want to see that looked at by someone who's passionate about this patch.

comment:20 by Sung-jin Hong, 16 years ago

Triage Stage: Ready for checkinAccepted

by terrex2, 16 years ago

Attachment: 3148.5.diff added

updated patch for rev.8463

comment:21 by terrex2, 16 years ago

Cc: terrex2 added

comment:22 by Marc Fargas, 16 years ago

The docs-refactor broke the patch, always up-to-date patch here in colours and raw

comment:23 by ramusus, 15 years ago

Cc: ramusus@… added

comment:24 by Jacob, 14 years ago

I'm busy and don't have time to explain this fully. However, as a note to myself and anyone who wants to try to channel my thinking, the syntax I'd like here would look like::

class MyModel(Model):
    foo = CharField(...)

    @foo.getter
    def get_foo(self):
        ...

    @foo.setter
    def set_foo(self, value):
        ...

I'll try to circle 'round back to this and explain more fully when I have time.

in reply to:  24 comment:25 by Marty Alchin, 14 years ago

Owner: changed from Marc Fargas to Marty Alchin
Patch needs improvement: set
Status: assignednew

Replying to jacob:

I'm busy and don't have time to explain this fully. However, as a note to myself and anyone who wants to try to channel my thinking, the syntax I'd like here would look like

It seems simple enough to channel that, actually. I assume this would work identically to the new property methods added in Python 2.6. I've been wondering about this myself lately, but didn't spend the time to work on it because I forgot this ticket already exists. I'll see what I can throw together this week.

I can think of a few questions that might need to be addressed along the way (at least one that definitely will), but I'll hold off until I have some working code before bringing them up. On a side note, if we mimic the property methods, I assume we'd also want to include @foo.deleter as well, which should be simple enough to throw in.

comment:26 by Marty Alchin, 14 years ago

After doing some work on this, I'll throw in one particular comment about naming. Python's built-in @property methods require that all the methods have the same name, because things go pretty wonky if you don't. They have their reasons, I'm sure, but I don't think that requirement makes a lot of sense in our situation. Instead, I'm going with the (much more obvious, IMO) approach of leaving any user-created methods in place and updating the field object to work as a descriptor. In the context of what a model definition looks like, I think that makes the most sense.

comment:27 by Daniel Hahler, 14 years ago

I think this a fundamental feature, and although not fluent in Python and Django, I've started working on it.

The proposed/attached patch implements the method suggested by jacob.

I've added tests for it and all the current tests still pass (there are oddities with admin_scripts, but that's with and without the patch).

by Daniel Hahler, 14 years ago

Patch against trunk r11791 - new approach

comment:28 by Alex Gaynor, 14 years ago

Some feedback:

a) A decorator should always return a callable, in this case it should return the original function (as Gulopine sugested), since the alternative leaves None on the class, which is really awkward IMO.

b) As Gulopine suggested I think the better approach is to make the Field object itself the descriptor when getter/setter/deleter are provided, not a property object.

c) This should be handled by Field.contribute_to_class, not the Metaclass.

d) I'd like to see significantly more extensive tests, particularly with things like:

  • Providing only a subset of the available options.
  • Providing a getter/setter with fields that have their own descriptor (FileField and the RelatedFields). This I suspect may require a large refactor of related.py, as Gulopine may have already found :)

comment:29 by Alex Gaynor, 14 years ago

Needs documentation: set

Oh, and

e) Documentation! :)

comment:30 by Daniel Hahler, 14 years ago

Thanks for your feedback.
a) getter/setter/deleter now return the original function
b) I do not get this: using a decorator should make the object behave as descriptor? Would that mean to have get/set/del always, but check there if a decorator should get called?
c) OK, moved. Much better. It took me a while to come that close already though.. ;)
d) 1. This should fail, at least when going the property route, my understanding is that you need to define all, otherwise the field is e.g. not writable. This would be different when using descriptors only, of course.

  1. Only fields derived from Field are supported currently. FileField does not come through Field - at least that's what my testing shows. Should they get handled?

e) I think the other points should get worked out before.. :)

by Daniel Hahler, 14 years ago

Attachment: django-3148.diff added

Slightly updated patch

comment:31 by Alex Gaynor, 14 years ago

The reason you aren't seeing FileField/RelatedFields working right now is that they provide their own descriptor, which overwrites yours I imagine. This is why Gulopine and I discussed integrating the descriptor into the Field itself (and only conditionally attaching it to the class) and then integrating the existing descriptors for Files and related objects into the class itself so everything works together correctly. You can find our discussion of this here: http://botland.oebfare.com/logger/django-dev/2009/12/6/1/#22:41-1918558 (extends onto the next page).

comment:32 by Alex Gaynor, 14 years ago

Thanks for all your work on this BTW!

comment:33 by Daniel Hahler, 14 years ago

FileField/RelatedFields work as before (at least passing tests - I have not tried it for real) - they are not affected by getter/setter currently (AFAICS).

My understanding had been that I'm not using a descriptor, but property/ies instead - I've just read that it's considered to be a descriptor, too.

The discussion you've linked sounds good, but still I'm not sure where you're heading.. :)

Therefore, a last patch from me (which does not use property() anymore), but adds a FieldDescriptor class. There's a lot to fix/straighten for sure, but just to get this out for discussion.

I'll now wait until Gulopine and others provide some feedback on this.

by Daniel Hahler, 14 years ago

Approach using FieldDescriptor class (and using decorators to decide if fields get wrapped into it or not)

comment:34 by jerf, 14 years ago

Using the model:

class GetSet(models.Model):
    field = models.CharField(max_length=200)

    @field.setter
    def field_setter(self, value):
        self._field = value + "_test_transform"
    @field.getter
    def field_getter(self):
        return self._field

we obtain with the latest patch "django-3148-approach-via-descriptors.diff" the following python REPL output:

Python 2.6.4 (r264:75706, Nov  8 2009, 19:34:30)
[GCC 4.3.3] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> from mysite.modeltest.models import GetSet
>>> example = GetSet(field = "test")
>>> example.field
'test_test_transform'
>>> example.save()
>>> example.id
1
>>> del example
>>> example = GetSet.objects.get(id = 1)
>>> example.field
u'test_test_transform_test_transform'
>>> example.save()
>>> del example
>>> example = GetSet.objects.get(id = 1)
>>> example.field
u'test_test_transform_test_transform_test_transform'
>>>

That is not shippable behavior. The setter must not apply during construction from a database load. Loading and immediately saving an object with no changes must be a no-op, in terms of the stored data. Anything else is asking for disaster.

I find myself wondering if perhaps the traffic on this bug is telling us that the interaction of properties when the values are being backed by a database store is just too complicated a concept to be worthwhile. When the setters and getters would apply is more subtle than it first appears, and perhaps "simple enough that it works as it first appears" (i.e., "no property behavior") is a better idea.

I am not being sarcastic or passive aggressive. I am seriously wondering this. I now think the documentation would have to explain this in more detail than I originally thought, which adds a chunk of complexity right where Django's documentation needs it least, and I'm no longer convinced that's worth it. (Plus, after reviewing the latest SVN code, I still do not see a way to do this correctly without setting the flag during construction like I did in my original patch, which was not an... appreciated addition to the model loading code.)

comment:35 by Ilya Semenov, 14 years ago

Wrong. Lack of a correct implementation does not mean the initial problem doesn't worth solving it. Logically, the desired behaviour is simple as hell - do not run setters/getters when Django does its ORM things (that is: object fetching, construction, saving, deleting, caching, etc.), but always call getters/setters when a model is used from from an external (business logic) code. I don't see anything strange or hard or inconsistent from the architectural point of view either. The ORM - which is aware of the essence of the models - should work with the data directly using internal structures (__dict__ or whatever), and all the other unaware code should be using the "frontend" properties.

The "simple enough that it works as it first appears" approach is ultimately doomed. If everyone was adopting this kind of thinking, there would be no ORM at all and we'd be running manual SQL queries which are "simple enough that they work as they first appear". Abstractions always leak and there will always be trade-offs, but that doesn't mean we should stop creating them. We should only stop when the gain is less than the cost, which is clearly not the case for getters and setters.

By the way, I am more and more thinking that the part of the confusion comes from that everyone is talking about BOTH getters and setters. In my opinion, we should have only setters. As the data is backed by a database, reading should always return what the database actually holds (with respect to Field.to_python(), of course). However, setting is different and needs to be able to be overriden indeed - because that allows to implement additional validation, transformation (e.g. proper formatting), and other things like logging.

One of the clear examples is using Model Forms. Right now, the whole powerful concept is barely useful for any non-trivial models, because you can't just call modelform.save() and expect it to call all setters properly -- instead, you always have to code all that manually. However, I can't imagine any example where a getter would be of any use.

comment:36 by Ilya Semenov, 14 years ago

I beg my pardon for not reading the "simple enough that it works as it first appears" sentence clearly. I'm taking my words back. Of course, the things should work as they appear. That just doesn't mean we can't achieve that behavior with custom setters. :)

comment:37 by Guilherme Gondim (semente) <semente@…>, 14 years ago

Cc: semente+djangoproject@… added

comment:38 by erikrose, 14 years ago

Cc: erikrose added

comment:39 by Alex Gaynor, 14 years ago

Owner: changed from Marty Alchin to Alex Gaynor
Status: newassigned

comment:40 by Chris Chambers, 13 years ago

Cc: Chris Chambers added

comment:41 by josh.smeaton@…, 13 years ago

One of my pet peeves is that Django models are almost always used as simple data containers. This is true for many frameworks and technologies though. What is wrong with defining methods on the class explicitly that are responsible for setting the correct fields, where there is logic associated with them? Allow the users of the model to decide whether they call the documented api of the model, or go directly to the fields.

comment:42 by Alex Gaynor, 13 years ago

Owner: Alex Gaynor removed
Status: assignednew

Unassigning, I haven't had the time to work on this.

comment:43 by Denilson Figueiredo de Sá, 13 years ago

Cc: denilsonsa@… added

comment:44 by Christopher Grebs, 13 years ago

Cc: Christopher Grebs added

comment:45 by Łukasz Rekucki, 13 years ago

Severity: normalNormal
Type: enhancementNew feature

comment:46 by noah, 13 years ago

Cc: noah added
Easy pickings: unset

by berdario, 13 years ago

Attachment: 3148-propertyfield.diff added

This approach works with m2m fields and inherited fields

by berdario, 13 years ago

Attachment: 3148-propertyfield2.diff added

slightly more complex approach, that on the other hand manages to obtain the same behaviour with m2m fields

by berdario, 13 years ago

Attachment: 3148-propertyfield3.diff added

Fixes to the previous patch

comment:47 by Michael Curry, 12 years ago

Cc: inactivist@… added
UI/UX: unset

comment:48 by Anssi Kääriäinen, 12 years ago

I have a feeling this ticket needs to be wontfixed. The reason is that it seems the setter should not be called in model.__init__ if the object comes from the DB. On the other hand, __setattr__ must be called for backwards compatibility reasons. I don't see how to achieve this: you can't assign to the attribute in __init__ - setter will not be called. You can't assign to __dict__ - setattr will not be called. In addition the solution must not cause any severe performance regressions to __init__

We have managed to live without this feature to this day. Maybe it is time to just let this feature go?

comment:49 by Aymeric Augustin, 12 years ago

Resolution: wontfix
Status: newclosed

Indeed, this could introduce subtle backwards incompatibilities for people who have overridden __setattr__. I don't believe this had been noticed until now.

An earlier comment claims that "the desired behaviour is simple as hell", but that isn't backed by any code and the analysis includes some hand-waving. I see that the latest patch, which is the 14th attempt at doing this correctly, still contains a comment saying it's a hack, and it alters __bases__. I'm not comfortable with such techniques, to say the least.

My recommendation would be to call the field _foo and have a foo property on your model that actually gets/sets _foo. This might seem unclean, but it's absolutely transparent, and the relation with the ORM is totally obvious. It's also immune to the multiple-execution problem.

If you have a better idea — and enough arguments to convince the core team that it doesn't have side effects such as the one found by Anssi — please make your case on django-developers. Thanks!

comment:50 by anonymous, 11 years ago

Anyone looking for a simple answer to this problem without using an underscore in the database could try:

http://www.korokithakis.net/posts/how-replace-django-model-field-property/

classMyClass(models.Model):
    _my_date=models.DateField(db_column="my_date")

@property
def my_date(self):
    return self._my_date

@my_date.setter
def my_date(self,value):
    if value>datetime.date.today():
        logger.warning("The date chosen was in the future.")
    self._my_date=value

Augustin's solution was half complete.

comment:51 by ariel17, 10 years ago

This solution does not work well on templates because they cannot see a field starting with "_". Also queries cannot be done on this field (for example, when writing a custom model manager) because referencing the field "_field" confuses the ORM with field joining.

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