Given a Django Model
class Sub(models.Model):
name = models.CharField(max_length=100)
size_in_inches = models.IntegerField(default=6)
class TunaSub(Sub):
fish_ingredient = models.CharField(max_length=10, default="Tuna")
class MeatballSub(Sub):
meat_ingredient = models.CharField(max_length=20, default="Meatball with Cheese")
I would like to access the attribute of the superclass for, say a __str__ method (in Python 3.x). How can I do so? Is this the correct solution?
class TunaSub(Sub):
fish_ingredient = models.CharField(max_length=10, default="Tuna")
def __str__(self):
return self.super().name
class MeatballSub(Sub):
meat_ingredient = models.CharField(max_length=20, default="Meatball with Cheese")
def __str__(self):
return self.super().name
Since you extend Sub, name is also a field of both TunaSub and MeatballSub. So you can simply use
def __str__(self):
return self.name
As a side note, since you are extending a concrete model, you are in fact creating three separate tables in the database (named sub, tuna_sub, and meatball_sub) which are connected via one-to-one relations. If you only want to reuse the field definitions in sub and not actually create a table for it, use an abstract base model class.
Related
It is possible to define a __str__ method on a model:
class Person(models.Model):
name = models.CharField(max_length=40)
hobbies = models.ManyToManyField(Hobby)
def __str__(self):
return self.name
But how to define something like this for an automatically generated table through a ManyToManyField? Objects that represent a relationship between a person and a hobby are written like this:
Person_hobbies object (1)
Note that I am not talking about the Hobby object itself, but the object that represents the relation between a Person and a Hobby.
But how to define something like this for an automatically generated table through a ManyToManyField?
You can define the junction table model with the through=… parameter [Django-doc]:
class Person(models.Model):
name = models.CharField(max_length=40)
hobbies = models.ManyToManyField(Hobby, through='PersonHobby')
def __str__(self):
return self.name
class PersonHobby(models.Model):
person = models.ForeignKey(Person, on_delete=models.CASCADE)
hobby = models.ForeignKey(Hobby, on_delete=models.CASCADE)
def __str__(self):
return f'A string with the {self.person} and the {self.hobby}'
Migrating to a junction model for a ManyToManyField is however not that easy. If it is possible, I would delete migrations that include the ManyToManyField and recreate a migration.
I'm developing Django application, and I have following error
'Sheep' object has no attribute _state
My models are constructed like this
class Animal(models.Model):
aul = models.ForeignKey(Aul)
weight = models.IntegerField()
quality = models.IntegerField()
age = models.IntegerField()
def __init__(self,aul):
self.aul=aul
self.weight=3
self.quality=10
self.age=0
def __str__(self):
return self.age
class Sheep(Animal):
wool = models.IntegerField()
def __init__(self,aul):
Animal.__init__(self,aul)
What I must do?
firstly, you must be very careful overriding __init__ to have non-optional arguments. remember it will be called every time you get an object from a queryset!
this is the correct code you want:
class Animal(models.Model):
#class Meta: #uncomment this for an abstract class
# abstract = True
aul = models.ForeignKey(Aul)
weight = models.IntegerField(default=3)
quality = models.IntegerField(default=10)
age = models.IntegerField(default=0)
def __unicode__(self):
return self.age
class Sheep(Animal):
wool = models.IntegerField()
I highly suggest setting the abstract option on Animal if you will only ever be using subclasses of this object. This ensures a table is not created for animal and only for Sheep (etc..). if abstract is not set, then an Animal table will be created and the Sheep class will be given it's own table and an automatic 'animal' field which will be a foreign key to the Animal model.
Django docs recommend against you to use __init__ method in models:
You may be tempted to customize the model by overriding the __init__ method. If you do so, however, take care not to change the calling signature as any change may prevent the model instance from being saved. Rather than overriding __init__, try using one of these approaches:
Add a classmethod on the model class
Add a method on a custom manager (usually preferred)
I would like to show one attribute from another class. The current class has a foreign key to class where I want to get the attribute.
# models.py
class Course(models.Model):
name = models.CharField(max_length=100)
degree = models.CharField(max_length=15)
university = models.ForeignKey(University)
def __unicode__(self):
return self.name
class Module(models.Model):
code = models.CharField(max_length=10)
course = models.ForeignKey(Course)
def __unicode__(self):
return self.code
def getdegree(self):
return Course.objects.filter(degree=self)
# admin.py.
class ModuleAdmin(admin.ModelAdmin):
list_display = ('code','course','getdegree')
search_fields = ['name','code']
admin.site.register(Module,ModuleAdmin)
So what i'm trying to do is to get the "degree" that a module has using the "getdegree". I read several topics here and also tried the django documentation but i'm not an experienced user so even I guess it's something simple, I can't figure it out. Thanks!
It is pretty straight forward.
Try this:
def getdegree(self):
return self.course.degree
Documentation here
You can do this safely because course is not a nullable field. If it were, you should have checked for existence of object before accessing its attribute.
I have read the definition in the official Django documentation, and I am still confused by what a Manager does.
The documentation says that they allow you to operate on database tables/models, but I still don't understand this.
Can someone explain managers and their role to me? An answer with an example would be preferable.
A manager is usually something hidden away from django programmers that django uses to interface between model code and the database backend.
When you query the django ORM, you do so through calls to
from my_app.models import MyModel
mms = MyModel.objects.all()
In this case, the objects part of the function is what is returned by the manager. If you wanted MyModel to only ever get blue MyModel instances (the database might contain red models too) then you could create a manager and hack your model thus
class BlueManager(models.Manager):
def get_query_set(self):
return super(BlueManager, self).get_query_set().filter(colour='Blue')
class MyModel(models.Model):
colour = models.CharField(max_length=64)
blue_objects = BlueManager()
and calling
MyModel.blue_objects.all()
would only return objects with colour as blue. Note, this is a very poor way to filter models!
One would usually need to modify a Manager interface if they were going to modify the QuerySets that a manager would usually return or if you needed to add "table" level queries (rather than regular django "row" level). The documentation for managers is quite complete and contains several examples.
Manager is some kind of 'gate' between application and database. One of nice thing is that you can define you own base queryset for model. For example: if you have model 'Book' with 'availability' field, you can prepare your own queryset, which filters specific kind of availability type:
models.py:
class AvailableBookManager(models.Manager):
def get_query_set(self):
return super(AvailableBookManager, self).get_query_set().filter(availability=1)
class Book(models.Model):
(...)#fields definition
objects = models.Manager() #default manager
available_objects = AvailableBookManager() #own manager
and now you can use like this:
books = Book.available_objects.all()
instead of using:
books = Book.objects.filter(available=1)
Definition
From the docs:
A Manager is a Django class that provides the interface between database query operations and a Django model.
In other words, in a Django model, the manager is the interface that interacts with the database. For example, when you want to retrieve objects from your database, you need to construct a QuerySet via a Manager on your model class.
By default, the manager is available through the Model.objects property. This manager is the django.db.models.Manager. However, it is very simple to extend it and change the default manager.
Custom managers
From the docs:
You can use a custom Manager in a particular model by extending the base Manager class and instantiating your custom Manager in your model.
There are two reasons you might want to customize a Manager (none of them are exclusive):
to add extra Manager methods
to modify the initial QuerySet the Manager returns
Example: adding extra methods to a Manager
from django.db import models
class DocumentManager(models.Manager):
def pdfs(self):
return self.filter(file_type='pdf')
def smaller_than(self, size):
return self.filter(size__lt=size)
class Document(models.Model):
name = models.CharField(max_length=30)
size = models.PositiveIntegerField(default=0)
file_type = models.CharField(max_length=10, blank=True)
objects = DocumentManager()
def __str__(self) -> str:
return self.name
Example: modifying the initial QuerySet the Manager returns
from django.db import models
class AuthorManager(models.Manager):
def get_queryset(self):
return super().get_queryset().filter(first_name__startswith='M')
class Author(models.Model):
first_name = models.CharField(max_length=100)
last_name = models.CharField(max_length=100)
email = models.EmailField()
objects = AuthorManager()
def __str__(self) -> str:
return f"{self.first_name} {self.last_name}"
Example: multiple managers at the same time
It is possible to have multiple managers for the same model.
from django.db import models
from django.db.models.functions import Length
class BookTitleManager(models.Manager):
def short_titles(self):
return self.annotate(length=Length('title')).filter(length__lte=20)
def long_titles(self):
return self.annotate(length=Length('title')).filter(length__gt=20, length__lte=30)
def very_long_titles(self):
return self.annotate(length=Length('title')).filter(length__gt=30)
def starts_with(self, letter):
return self.filter(title__startswith=letter)
class BookPagesManager(models.Manager):
def small_books(self):
return self.filter(pages__lt=200)
def medium_books(self):
return self.filter(pages__gte=200, pages__lt=300)
def large_books(self):
return self.filter(pages__gte=300, pages__lte=500)
class Book(models.Model):
title = models.CharField(max_length=255)
author = models.CharField(max_length=255)
pages = models.IntegerField()
objects = models.Manager()
titles = BookTitleManager()
sizes = BookPagesManager()
def __str__(self) -> str:
return f'{self.title} by {self.author}'
In the previous code sample, there are 3 managers: the default models.Manager, BookTitleManager and BookPagesManager assigned to objects, titles and sizes respectively.
The problem with the previous managers is that you cannot chain them as follows:
>>> Book.titles.long_titles().starts_with('P')
AttributeError: 'QuerySet' object has no attribute 'starts_with'
Example: Custom manager and queryset (chains are allowed)
If you want to chain methods defined in managers, you should define a custom QuerySet as follows:
from django.db import models
from django.db.models.functions import Length
class AuthorQuerySet(models.QuerySet):
def long_first_name(self):
return self.annotate(length=Length("first_name")).filter(length__gte=10)
def short_last_name(self):
return self.annotate(length=Length("last_name")).filter(length__lte=10)
class AuthorManager(models.Manager):
def get_queryset(self):
return AuthorQuerySet(self.model, using=self._db)
def long_first_name(self):
return self.get_queryset().long_first_name()
def short_last_name(self):
return self.get_queryset().short_last_name()
class Author(models.Model):
first_name = models.CharField(max_length=100)
last_name = models.CharField(max_length=100)
email = models.EmailField()
objects = AuthorManager()
def __str__(self) -> str:
return f"{self.first_name} {self.last_name}"
Example: Custom queryset used as manager
When defining just custom QuerySets in the manager, it is possible to simply extend the QuerySet and set it as the manager.
from django.db import models
from django.db.models.functions import Length
class PublisherQuerySet(models.QuerySet):
def long_name(self):
return self.annotate(length=Length("name")).filter(length__gte=15)
def long_address(self):
return self.annotate(length=Length("address")).filter(length__gte=25)
def country_starts_with(self, letter):
return self.filter(country__startswith=letter)
class Publisher(models.Model):
name = models.CharField(max_length=100)
address = models.CharField(max_length=255)
country = models.CharField(max_length=100)
objects = PublisherQuerySet.as_manager() # uses QuerySet as Manager
def __str__(self) -> str:
return self.name
Also to keep in mind
If you want to use objects as a field name, or if you want to use a name other than objects for the Manager, you can rename it on a per-model basis. To rename the Manager for a given class, define a class attribute of type models.Manager() on that model.
class Document(models.Model):
name = models.CharField(max_length=30)
size = models.PositiveIntegerField(default=0)
file_type = models.CharField(max_length=10, blank=True)
stuff = models.Manager()
def __str__(self) -> str:
return self.name
In the previous code sample, calling Document.objects will generate an AttributeError exception because the default manager has been renamed, what will work now is Document.stuff.
Managers are accessible only via model classes, rather than from model instances, to enforce a separation between “table-level” operations and “record-level” operations.
If a model has a ForeignKey, instances of the foreign-key model will have access to a Manager that returns all instances of the first model. By default, this Manager is named FOO_set, where FOO is the source model name, lowercased.
I'm looking to do this:
class Place(models.Model):
name = models.CharField(max_length=20)
rating = models.DecimalField()
class LongNamedRestaurant(Place): # Subclassing `Place`.
name = models.CharField(max_length=255) # Notice, I'm overriding `Place.name` to give it a longer length.
food_type = models.CharField(max_length=25)
This is the version I would like to use (although I'm open to any suggestion):
http://docs.djangoproject.com/en/dev/topics/db/models/#id7
Is this supported in Django? If not, is there a way to achieve similar results?
Updated answer: as people noted in comments, the original answer wasn't properly answering the question. Indeed, only the LongNamedRestaurant model was created in database, Place was not.
A solution is to create an abstract model representing a "Place", eg. AbstractPlace, and inherit from it:
class AbstractPlace(models.Model):
name = models.CharField(max_length=20)
rating = models.DecimalField()
class Meta:
abstract = True
class Place(AbstractPlace):
pass
class LongNamedRestaurant(AbstractPlace):
name = models.CharField(max_length=255)
food_type = models.CharField(max_length=25)
Please also read #Mark answer, he gives a great explanation why you can't change attributes inherited from a non-abstract class.
(Note this is only possible since Django 1.10: before Django 1.10, modifying an attribute inherited from an abstract class wasn't possible.)
Original answer
Since Django 1.10 it's
possible!
You just have to do what you asked for:
class Place(models.Model):
name = models.CharField(max_length=20)
rating = models.DecimalField()
class Meta:
abstract = True
class LongNamedRestaurant(Place): # Subclassing `Place`.
name = models.CharField(max_length=255) # Notice, I'm overriding `Place.name` to give it a longer length.
food_type = models.CharField(max_length=25)
No, it is not:
Field name “hiding” is not permitted
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.
That is not possible unless abstract, and here is why: LongNamedRestaurant is also a Place, not only as a class but also in the database. The place-table contains an entry for every pure Place and for every LongNamedRestaurant. LongNamedRestaurant just creates an extra table with the food_type and a reference to the place table.
If you do Place.objects.all(), you also get every place that is a LongNamedRestaurant, and it will be an instance of Place (without the food_type). So Place.name and LongNamedRestaurant.name share the same database column, and must therefore be of the same type.
I think this makes sense for normal models: every restaurant is a place, and should have at least everything that place has. Maybe this consistency is also why it was not possible for abstract models before 1.10, although it would not give database problems there. As #lampslave remarks, it was made possible in 1.10. I would personally recommend care: if Sub.x overrides Super.x, make sure Sub.x is a subclass of Super.x, otherwise Sub cannot be used in place of Super.
Workarounds: You can create a custom user model (AUTH_USER_MODEL) which involves quite a bit of code duplication if you only need to change the email field. Alternatively you can leave email as it is and make sure it's required in all forms. This doesn't guarantee database integrity if other applications use it, and doesn't work the other way around (if you want to make username not required).
See https://stackoverflow.com/a/6379556/15690:
class BaseMessage(models.Model):
is_public = models.BooleanField(default=False)
# some more fields...
class Meta:
abstract = True
class Message(BaseMessage):
# some fields...
Message._meta.get_field('is_public').default = True
My solution is as simple as next monkey patching, notice how I changed max_length attribute of name field in LongNamedRestaurant model:
class Place(models.Model):
name = models.CharField(max_length=20)
class LongNamedRestaurant(Place):
food_type = models.CharField(max_length=25)
Place._meta.get_field('name').max_length = 255
Pasted your code into a fresh app, added app to INSTALLED_APPS and ran syncdb:
django.core.exceptions.FieldError: Local field 'name' in class 'LongNamedRestaurant' clashes with field of similar name from base class 'Place'
Looks like Django does not support that.
This supercool piece of code allows you to 'override' fields in abstract parent classes.
def AbstractClassWithoutFieldsNamed(cls, *excl):
"""
Removes unwanted fields from abstract base classes.
Usage::
>>> from oscar.apps.address.abstract_models import AbstractBillingAddress
>>> from koe.meta import AbstractClassWithoutFieldsNamed as without
>>> class BillingAddress(without(AbstractBillingAddress, 'phone_number')):
... pass
"""
if cls._meta.abstract:
remove_fields = [f for f in cls._meta.local_fields if f.name in excl]
for f in remove_fields:
cls._meta.local_fields.remove(f)
return cls
else:
raise Exception("Not an abstract model")
When the fields have been removed from the abstract parent class you are free to redefine them as you need.
This is not my own work. Original code from here: https://gist.github.com/specialunderwear/9d917ddacf3547b646ba
Maybe you could deal with contribute_to_class :
class LongNamedRestaurant(Place):
food_type = models.CharField(max_length=25)
def __init__(self, *args, **kwargs):
super(LongNamedRestaurant, self).__init__(*args, **kwargs)
name = models.CharField(max_length=255)
name.contribute_to_class(self, 'name')
Syncdb works fine. I dont tried this example, in my case I just override a constraint parameter so ... wait & see !
I know it's an old question, but i had a similar problem and found a workaround:
I had the following classes:
class CommonInfo(models.Model):
image = models.ImageField(blank=True, null=True, default="")
class Meta:
abstract = True
class Year(CommonInfo):
year = models.IntegerField()
But I wanted Year's inherited image-field to be required while keeping the image field of the superclass nullable. In the end I used ModelForms to enforce the image at the validation stage:
class YearForm(ModelForm):
class Meta:
model = Year
def clean(self):
if not self.cleaned_data['image'] or len(self.cleaned_data['image'])==0:
raise ValidationError("Please provide an image.")
return self.cleaned_data
admin.py:
class YearAdmin(admin.ModelAdmin):
form = YearForm
It seems this is only applicable for some situations (certainly where you need to enforce stricter rules on the subclass field).
Alternatively you can use the clean_<fieldname>() method instead of clean(), e.g. if a field town would be required to be filled in:
def clean_town(self):
town = self.cleaned_data["town"]
if not town or len(town) == 0:
raise forms.ValidationError("Please enter a town")
return town
You can not override Model fields, but its easily achieved by overriding/specifying clean() method. I had the issue with email field and wanted to make it unique on Model level and did it like this:
def clean(self):
"""
Make sure that email field is unique
"""
if MyUser.objects.filter(email=self.email):
raise ValidationError({'email': _('This email is already in use')})
The error message is then captured by Form field with name "email"