Django model fields from constants? - python

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.

Related

Is there a way to reference variable name as a string in that variable's declaration in Python?

For my Django application, I have a dictionary of field names (keys) mapped to help texts (values) (which I read in from a csv file). In models.py, I want to retrieve the appropriate help text for each field name.
If I have a sample dictionary like the following, with an entry for each field:
test_dict = {
'host_name': 'The name of a host',
...
}
And my models.py looks like this:
class Host_main(models.Model):
host_name = models.CharField(max_length=20, unique=True, help_text=test_dict['host_name'])
def __str__(self):
return self.host_name
Is there a way to call the variable name (host_name) dynamically in each help_text definition?
I do have the option to do something like the following:
from varname import nameof
host_name = models.CharField(max_length=20, unique=True, help_text=test_dict[nameof(host_name)])
But if possible, I'd like to reference the current variable name with something consistent to avoid typing out the field name a second time, like help_text=test_dict[nameof(**this**)] in pseudocode.
Thanks!
Let's simplify your question a little bit, and let's remove the django part first. Let's say you have a class with name A, then you can set an attribute on A with
class A: pass
A.foo = 'bar'
Alternatively, you can also do
setattr(A,'bar','baz')
You can verify that those are indeed equivalent by checking
print(A.foo) # bar
print(A.bar) # baz
So if you are defining a normal class, you can pretty much just loop through your dict and set things with setattr calls, in which case you have the control of the variable name.
And when we bring Django into the question, things are a bit more complicated...There are some very strange and magical things happening when you initialize a Model class. Basically it does a lookup on things already defined, and transforms all the defined fields through the pretty much public private _meta API. This means that adding class attributes outside of definition time won't work, and you might have to use a bit of a hack to construct a class directly through its metaclass (in case you are not familiar with Python's metaclasses, a class is basically an instance of its metaclass).
The basic syntax for dynamically constructing a class is through the type call with 3 arguments: type(name, bases, dict). In your example,
class Host_main(models.Model):
host_name = models.CharField(max_length=20, unique=True, help_text=test_dict['host_name'])
def __str__(self):
return self.host_name
is the equivalent of
def Host_main_str(self): # the name here doesn't really matter unless you care about the __name__ attribute of the function, which you can overwrite.
return self.host_name
Host_main = type('Host_main', (models.Model,), {
'__str__': Host_main_str,
'host_name': models.CharField(max_length=20, unique=True, help_text=test_dict['host_name'])
})
It's actually how django constructs all their QuerySet classes (there were some more crazy things django did with dynamic class constructions if I remember correctly but I couldn't find a good example)
So, you can do things like
attr_dict = {'__str__':'Host_main_str'}
for name, help_text in test_dict.values():
attr_dict[name] = models.CharField(max_length=20, unique=True, help_text=help_text)
Host_main = type('Host_main', (models.Model,), attr_dict)
if you really insist on loop through a dictionary.

django simple history - using model methods?

I'm using django-simple-history:
http://django-simple-history.readthedocs.io/en/latest/
I have a model, which I would like to apply its methods on an historical instance. Example:
from simple_history.models import HistoricalRecords
class Person(models.Model):
firstname = models.CharField(max_length=20)
lastname = models.CharField(max_length=20)
history = HistoricalRecords()
def fullName(self):
return firstname + lastname
person = Person.objects.get(pk=1) # Person instance
for historyPerson in person.history:
historyPerson.fullName() # wont work.
Since the class HistoricalPerson does not inherit the methods of Person. But using Person methods actually make sense, since they share the same fields..
Any solution for this? I'd prefer something simple, not like duplicating every method in my models for the history instances..
I found another workaround (maybe it's just the addon had been updated and got this feature). It's based on the documentation: adding-additional-fields-to-historical-models
HistoricalRecords field accepts bases parameter which sets a class that history objects will inherit. But you can't just set bases=[Person] inside Person class description, because it's not yet initialized.
So I ended up with an abstract class, which is inherited by both Person class and HistoricalRecords field. So the example from the question would look like:
class AbstractPerson(models.Model):
class Meta:
abstract = True
firstname = models.CharField(max_length=20)
lastname = models.CharField(max_length=20)
def fullName(self):
return firstname + lastname
class Person(AbstractPerson):
history = HistoricalRecords(bases=[AbstractPerson])
And now history objects can use fullName method.
For anyone else having the same problem, I made it work by calling the method from the original class on the historical record object. So for the example in the question, a solution could be:
for historyPerson in person.history:
Person.fullName(historyPerson)
This works because methods are very much like functions in Python, except that when you call a method on an instance, the instance is implicitly passed as the first parameter for the method. So if you have a class like:
class Foo:
def method(self):
....
doing
f = Foo()
f.method()
is the same as:
f = Foo()
Foo.method(f)
I don't know exactly why simple-history does not copy the original model's methods though. One reason might be that since it allows you to exclude fields to be recorded, having the original methods might not make sense, since a method might not work if it uses fields that are not recorded in the historical record.

ModelChoiceField: how to show attributes and not objects?

I have two tables (Subject and Languae) with only one attribute, subject and language, each. In the relative form's fields I want to see a dropdown menu with the value of the attribute but with this code:
lang = forms.ModelChoiceField(queryset=Language.objects.order_by('?'), required=False, label='What language want to search?')
subject = forms.ModelChoiceField(queryset=Subject.objects.order_by('?'), required=False, label='Whitch subject you want to search?')
I see the dropdown menu filled of Subject object and Language object which are identical from one onother.
How can i show the actual value of the object?
The simplest solution is to implement your Language and Subject models __unicode__ method to make it return the attribute you want to display (or any unicode string built upon any combination of attributes or whatever). In your case:
class Subject(models.Model):
subject = models.CharField(....)
def __unicode__(self):
return self.subject
and ditto for Language
For more advanced usage, this is documented here: https://docs.djangoproject.com/en/1.6/ref/forms/fields/#modelchoicefield
Note that you don't really have to create a ModelChoiceField subclass to override label_from_instance - you can also just monkeypatch the ModelChoiceField instance with a lambda in your form's __init__

Django model fields: reference to self on default keyword

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.

Using related_name correctly in Django

I have two models that are related together using ForeignKey and related_name is used. Here is an example.
class Student(models.Model):
name = models.CharField(max_length=255)
birthday = models.DateField(blank=True)
class Class(models.Model):
name = models.CharField(max_length=255)
student = models.ForeignKey(Student,
related_name='classes',
null=True)
def __unicode__(self):
return self.name
For example, I would like to access the class name.
This is what i tried.
john = Student.objects.get(username = 'john')
print john.classes.name
nothing's get printed.
But when i try john.classes
i get django.db.models.fields.related.RelatedManager object at 0x109911410. This is shows that they are related. But i would like to get the class name.
Am i doing something wrong? How do i access the name of the class using related_name? Need some guidance.
Yes, classes is a manager. It can be several classes for one teacher. So to output their names you should do:
john = Student.objects.get(username='john')
for class2 in john.classes.all():
print class2.name
If you want only one class for one student then use one-to-one relation. In this case you can access the related field with your method.
Just be aware: you are defining a 1-many relationship. Thus, student could have multiple classes, therefore john.classes.name cannot work, since you have not specified the class of which you want to have the name. in john.classes "classes" is just a manager that you can use like any other Django Model Manager. You can do a john.classes.all() (like sergzach propsed), but also things like john.classes.get(...) or john.classes.filter(...).
you can do like this to access the first row in the table
john = Student.objects.get(username = 'john')
john.classes.all().first().name # to access first row
john.classes.all().last().name # to access last row
in the above example you don't want to iterate over the objects
it will give you the name of the class in the first row

Categories