I've been having problems to understand this and to come up with a way of doing a reference to self inside the default keyword of a model field:
Here is what I have:
class Bank(models.Model):
number = models.CharField(max_length=10)
class Account(models.Model):
bank = models.ForeignKey(Bank, related_name="accounts")
number = models.CharField(max_length=20)
created = models.DateTimeField(auto_now_add=True)
creator = models.ForeignKey(User)
# This is the guy
special_code = models.CharField(max_length=30, default='%s-%s' % (self.number, self.bank.number))
So I'm trying to access self inside the class definition, which seems to not work out because python doesn't know where self is since its not an object yet.
I've tried different things like:
special_code = models.CharField(max_length=30, default='%s-%s' % (number, bank.number))
But in this case it doesn't recognize bank.number because bank its only a property with models.ForeignKey.
I've tried also using a method inside the Account class:
def bank_number(self):
return self.bank.number
and then:
special_code = models.CharField(max_length=30, default='%s-%s' % (number, bank_number()))
That was kinda dumb because it still needs self.
Is there a way I can do this?
I need it to store the number inside the database, so using a method like this wont help:
def special_number(self):
return '%s-%s' % (self.number, self.bank.number)
I don't think there's any way to access self in the default callable. There's a couple of other approaches to set your field's value:
If you don't want the user to be able to change the value, override the model's save method and set it there.
If the default is just a suggestion, and you do want to allow the user to change it, then override the model form's __init__ method, then you can access self.instance and change set the field's initial value.
Instead of specifying a default for the field you probably want to override the save() method and populate the field right before storing the object in the database. The save() method also has access to self. Here is an example in the docs for that:
https://docs.djangoproject.com/en/dev/topics/db/models/#overriding-model-methods
As already answered, override the save() method of your model to assign a value to special_code. The default option of a field is not meant to depend on other fields of the model, so this will not work.
Also, have a look at the editable option, if you don't want the field to be edited.
special_code = models.CharField(max_length=30, editable=False)
Will prevent the field to be rendered in ModelForms you create from the model.
Related
I have a simple Model. I want to calculate a default value for one of its fields (let's call it score) based on some of the fields of the same model:
My ideal way to do it is as so:
class ModelA(models.model):
field_1 = models.IntegerField()
field_1 = models.IntegerField()
score = models.IntegerField(default=self.calculate_default())
def calculate_default(self):
default = (self.field_1 + self.field_2) / 2
return default
Problem: the calculate_default method does not have access to self.
One solution I tried was overriding the save() method. The problem is that it prevents the user to further change the score field.
Another method I searched was to override the inti of the class. But according to django it might have consequences if not implemented correctly.
What is the cleanest way to achieve this? How can I set the default of a field to be calculated from other fields of the same model such that it could be later changed by the user?
I have a hunch that classmethod is the way to go but I haven't been able to pull it off yet.
the calculate_default method does not have access to self. One solution I tried was overriding the save() method. The problem is that it prevents the user to further change the score field.
Actually its the right approach. But you can put a check in save() method if the value of score is empty or not:
def save(self, *args, **kwargs):
if self.score == None:
self.score = self.calculate_default()
super().save(*args, **kwargs)
FYI, in classmethod, you can't access self or the ModelA object. If the class method you intend to use is something not dependent on the current instance of ModelA's fields or totally independent value(for example: current date datetime.now), then you can use that.
How to define a Django model field in constant and use everywhere.
For example, if I have a model like:-
class Author(models.Model):
first_name = models.CharField(max_length=30)
last_name = models.CharField(max_length=40)
email = models.EmailField()
And what I want to do is define constant for fields in Author model and provide the constant instead of field name in model like:-
KEY_FIRST_NAME = 'first_name'
KEY_LAST_NAME = 'last_name'
KEY_EMAIL = 'email'
And Author model should use the constant instead of exact key like:-
class Author(models.Model):
KEY_FIRST_NAME = models.CharField(max_length=30)
KEY_LAST_NAME = models.CharField(max_length=40)
KEY_EMAIL = models.EmailField()
How to do something like this, direct assignment to constant won't work here.
I want to store all the field name in constant, and everywhere when it required I want to use the constant instead of string field name.
The purpose of doing this is If there is any change in filed name in future version then I want to only change at one place and it should reflect on all the places.
If it is not possible or it will make code too complex as suggested by one approach by #dirkgroten than what can be the best practice to define the model field as constant and use them in other places (other than inside models like if we are referring those field for admin portal or any other place).
Short answer: you can't do this in Python, period (actually I don't think you could do so in any language but someone will certainly prove me wrong xD).
Now if we go back to your real "problem" - not having to change client code if your model's fields names are ever to change - you'd first need to tell whether you mean "the python attribute name" or "the underlying database field name".
For the second case, the database field name does not have to match the Python attribute name, Django models fields take a db_column argument to handle this case.
For the first case, I'd have to say that it's a very generic (and not new by any mean) API-design problem, and the usual answer is "you shouldn't change names that are part of your public API once it's been released". Now sh!t happens and sometimes you have to do it. The best solution here is then to use computed attributes redirecting the old name to the new one for the deprecation period and remove them once all the client code has been ported.
An example with your model, changing 'first_name' to 'firstname':
class Author(models.Model):
# assuming the database column name didn't change
# so we can also show how to us `db_column` ;)
firstname = models.CharField(
max_length=30,
db_column='first_name'
)
#property
def first_name(self):
# shoud issue a deprecation warning here
return self.firstname
#first_name.setter
def first_name(self, value):
# shoud issue a deprecation warning here
self.firstname = value
If you have a dozen fields to rename you will certainly want to write a custom descriptor (=> computed attribute) instead to keep it dry:
class Renamed(object):
def __init__(self, new_name):
self.new_name = new_name
def __get__(self, instance, cls):
if instance is None:
return self
# should issue a deprecation warning here
return getattr(instance, self.new_name)
def __set__(self, instance, value):
# should issue a deprecation warning here
setattr(instance, self.new_name, value)
class Author(models.Model):
firstname = models.CharField(
max_length=30,
db_column='first_name'
)
first_name = Renamed("firstname")
I think the following information could prove beneficial:
To achieve this you first need to think how you can define class parameters from strings. Hence, I came across a way to dynamically create derived classes from base classes: link
Particularly this answer is what I was looking for. You can dynamically create a class with the type() command.
From here on, search how to integrate that with Django. Unsurprisingly someone has tried that already - here.
In one of the answers they mention dynamic Django models. I haven't tried it, but it might be what you are searching for.
As we can define the __unicode__ representation of a model,
Is there a way to define the same for a model field ? (or is it a bad idea ?)
You can add your own methods. For example, when you use choices for a field, django automatically creates a get_FIELD_display method for the FIELD.
class Something(models.Model):
name = models.CharField(max_length=25)
def get_name_uppercase(self):
return self.name.upper()
then when you have
something = Something.get(id=1)
you can access it via
something.get_name_uppercase()
Where should I overwrite method add() for ManyToMany related fields.
Seems like it is not manager 'objects' of my model. Because when we are adding new relation for ManyToMany fields we are not writing Model.objects.add().
So what I need it overwrite method add() of instance. How can I do it?
Edit:
So i know that there is ManyRelatedManager. One thing remain how can i overwrite it?
Sorry... not overwrite, but assign it in my Model by default.
http://docs.djangoproject.com/en/1.2/topics/db/managers/#custom-managers
You can create any number of managers for a Model.
You can subclass a ManyRelatedManager and assign it to the Model.
This example may be what you're looking for
# Then hook it into the Book model explicitly.
class Book(models.Model):
title = models.CharField(max_length=100)
author = models.CharField(max_length=50)
objects = models.Manager() # The default manager.
dahl_objects = DahlBookManager() # The Dahl-specific manager.
The objects manage is the default. Do not change this.
The dahl_objects is a customized manager. You can have any number of these.
The default choice field display of a reference property in appengine returns the choices
as the string representation of the entire object. What is the best method to override this behaviour? I tried to override str() in the referenced class. But it does not work.
I got it to work by overriding the init method of the modelform to pick up the correct fields as I had to do filtering of the choices as well.
The correct way would be to override the __unicode__ method of the class, like:
def __unicode__(self):
return self.name
where name is the value that you want to display.