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)
Related
I've got proxy classes which have been created mainly to implement custom filtering, but there are some other fairly small custom methods as well, and they will be expanded to provide other custom logic as well.
So say I have models:
class Videos(models.Model):
title = models.CharField(max_length=200)
publisher = models.Charfield(max_length=100)
release_date = models.DateField()
class Superheroes(Videos):
objects = SuperheroesManager()
class Meta:
proxy = True
class Recent(Videos):
objects = RecentManager()
class Meta:
proxy = True
and model managers:
class SuperheroesManager():
def get_queryset(self):
return super().get_queryset().filter(publisher__in=['Marvel','DC'])
class RecentManager():
def get_queryset(self):
return super().get_queryset().filter(release_date__gte='2020-01-01')
On the front end a user may pick a category which corresponds to one of the proxy classes. What would be the best way to maintain a mapping between the category which is passed to the view and the associated proxy class?
Currently I have an implicit dependency whereby the category name supplied by the front end must be the same as the proxy class name, allowing for a standard interface in the view:
def index(request, report_picked)
category = getattr(sys.modules[__name__], report_picked)
videos = category.objects.all()
I'd like to move away from this implicit dependency, but not sure what the best way would be.
I wouldn't want to maintain a dictionary and can't use a factory method either as that should return a fully initialised object whereas I just need the class returned.
What would be the best way to implement this?
I've decided to set the category name used by the front end as a class variable:
class Superheroes(Videos):
category = 'superheroes'
objects = SuperheroesManager()
class Meta:
proxy = True
And so the view just loops through all the models, and returns the model whose category matches the provided value from the front end:
from django.apps import apps
def index(request, report_picked):
for model in apps.get_models():
try:
print(f"Report picked: {report_picked}, model: {model.name}")
if model.category == report_picked.lower():
category = model
break
except AttributeError:
pass
I'd be curious to know if there is any better alternatives though.
For example I have 2 main models in my django app:
class Employee(Model):
name = CharField(max_length=50)
class Client(Model):
title = CharField(max_length=50)
Abstract base class for phones:
class Phone(Model):
number = CharField(max_length=10)
class Meta:
abstract = True
Inherited separate classes for Employee and for Client:
class EmployeePhone(Phone):
employee = ForeignKey(Employee, on_delete=CASCADE, related_name='employee_phones')
class ClientPhone(Phone):
client = ForeignKey(Client, on_delete=CASCADE, related_name='client_phones')
It works but I don't like it, I would prefer to keep just one Phone model instead of 3. I know I could use Generic-Models but unfortunately that's not longer an option, because my app is actually REST-API and it seems to be impossible to create Generic-Object while creating Parent-Object. So is there any solution to keep things clean and DRY ?
Other answers present good ideas how you can normalise the database, but if you'd like to keep the schema the same and just avoid repeating the same thing in the code, maybe a custom field subclass is what you're after?
Example:
# fields.py
class PhoneField(models.CharField):
def __init__(self, **kwargs):
kwargs.setdefault('max_length', 50)
...
super().__init__(**kwargs)
# models.py
class Employee(models.Model):
phone = PhoneField()
How about keeping 1 Phone class and linking Employee and Customer to Phone via a Foreign Key and removing the abstract :).
How about moving EmployeePhone (ClientPhone) as a ManyToManyField in Employee (Client) model related to Phone model?
class Employee(Model):
name = CharField(max_length=50)
phones = ManyToManyField(Phone, ...)
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.
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"