ModelChoiceField: how to show attributes and not objects? - python

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__

Related

Django model fields from constants?

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.

Django - Model field define unicode

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()

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.

Django - how to make required to implement field?

I'm buildibg some abstract model for about 10 models. I need to make, somehow, that 1 field is not declared in abstract model, but MUST be declared in inheriting models.
How to do that? Is there any way to use NotImplementedError?
I am afraid there isn't an easy way to achieve that, if possible at all, without digging deep into Django.
The main reason is that Field name "hiding" is not permitted in Django. What this means is that if you want to declare an abstract attribute in the base abstract class that is a Field instance, you will not be able to rewrite it in the child classes contrary to the normal Python class inheritance paradigm. To quote from the doc:
In normal Python class inheritance, it is permissible for a child class to override any attribute from the parent class. In Django, this is not permitted for attributes that are Field instances (at least, not at the moment). If a base class has a field called author, you cannot create another model field called author in any class that inherits from that base class.
Overriding fields in a parent model leads to difficulties in areas such as initializing new instances (specifying which field is being initialized in Model.init) and serialization. These are features which normal Python class inheritance doesn't have to deal with in quite the same way, so the difference between Django model inheritance and Python class inheritance isn't arbitrary.
This restriction only applies to attributes which are Field instances. Normal Python attributes can be overridden if you wish. It also only applies to the name of the attribute as Python sees it: if you are manually specifying the database column name, you can have the same column name appearing in both a child and an ancestor model for multi-table inheritance (they are columns in two different database tables).
Django will raise a FieldError if you override any model field in any ancestor model.
However, if the attribute is not a Field instance (very unlikely though), you will be able to achieve exactly what you want by using using #property decorator. Something like this should work:
class Person(models.Model):
def __init__(self, *args, **kwargs):
super(Person, self).__init__(*args, **kwargs)
self.last_name
first_name = models.CharField(max_length=30)
#property
def last_name(self):
raise NotImplementedError
class Meta:
abstract = True
class Student(Person):
home_group = models.CharField(max_length=5)
last_name = "Doe" # "models.CharField()" will not work!
class BadStudent(Person):
home_group = models.CharField(max_length=5)
# "NotImplmentedError" will be raised when instantiating BadStudent()
You may also want to take a look at abc.abstractproperty. I am not sure how it would work with Django's model inheritance though.
Why would you want to do it?? Which are the reasons the common field cannot be declared in the AbstractModel??
If you really want to do it, use the instructions here:
add methods in subclasses within the super class constructor

Django model class and custom property

today a weird problem occurred to me:
I have a model class in Django and added a custom property to it that shall not be saved into the database and therefore is not representative in the model's structure:
class Category(models.Model):
groups = models.ManyToManyField(Group)
title = defaultdict()
Now, when I'm within the shell or writing a test and I do the following:
c1 = Category.objects.create()
c1.title['de'] = 'german title'
print c1.title['de'] # prints "german title"
c2 = Category.objects.create()
print c2.title['de'] # prints "german title" <-- WTF?
It seems that 'title' is kind of global. If I change the title to a simple string it works as expected, so it has to do something with the dict? I also tried setting title as a property:
title = property(_title)
But that did not work, too. So, how can I solve this? Thank you in advance!
EDIT:
Here is the intention of the base problem to provide you with a better look at the whole surrounding environment as requested:
In our model structure, we have a model class that stores translations. This class is unbound from all the other classes that have relations with each other. The translation class stores the translated value, a language key, a translation key and the package and class the translation belongs to. Some model classes can have properties that can be translated into different languages. These properties are not mapped within the Django model structure as this is not truly possible in our eyes. Each of these classes with translatable properties, let's call them translatable, can have one or more of these properties. That's what the translation key is for. E.g. if there is a class Category with a translatable property "title", the model translation will store "module.somewhere.Category" as package/class, "title" as translation key, and e.g. for german the translation value "Kategorie" and the language key "de".
My aim is to ease the access to these properties. So all these model classes inherit from a plain class called "Translatable". It has a method for resolving the module path and name of the class (for the later storing within the translation database table) and a "_propertize" method that takes the name of the property. Properties instantiate a class "Translator" that is unique for each translatable property name. This class does the resolving of the real translation value from the translation model class and some stuff for automatically resolving the translation of the currently chosen language.
Don't do it that way. Your title attribute is completely "global". It's part of the class, not part of each instance.
Do something like this.
class Category(models.Model):
groups = models.ManyToManyField(Group)
#property
def title(self):
return self._title
def save( self, *args, **kw ):
try:
self._title
except AttributeError:
self._title= defaultdict()
super( Category, self ).save( *args, **kw )
If you could define your actual use case, it might be possible to simplify this a great deal.

Categories