So I'm struggling with ordering the choices within an InlinePanel (for an orderable) on my site. In the admin page, when adding a new item, the options are presented in the order they were added to the site (so, essentially the 'id' for that item); this is less than ideal considering there are hundreds of options presented in a manner that is not user friendly.
I'm assuming this needs to be defined as ordering meta within the orderable, but I can't seem to get it to work. This is what my orderable looks like:
class RelatedPeople(Orderable):
service = ParentalKey('service.Services', related_name='related_person')
person = models.ForeignKey('person.People', null=True, on_delete=models.SET_NULL, related_name='related_service')
panels = [
FieldPanel('person')
]
I've tried the following with no success:
class Meta:
ordering = 'person'
and, trying to append the field within 'person' that I want to sort by, 'name':
class Meta:
ordering = 'person.name'
There must be an obvious way to solve this that I'm over looking. A default sort order of the 'id' (in this case, for 'person.People') is rarely ever going to be suitable from the perspective of the content creator.
Any advice would be greatly appreciated!
Thanks in advance,
Rob
Person model should have:
ordering = ['name']
instead of
ordering = 'name'
And your Orderable object should have it's meta changed to
class Meta(Orderable.Meta):
Via Django Docs, this is the example of abstract base classes, and ordering:
Meta and multi-table inheritance¶
In the multi-table inheritance situation, it doesn’t make sense for a
child class to inherit from its parent’s Meta class. All the Meta
options have already been applied to the parent class and applying
them again would normally only lead to contradictory behavior (this is
in contrast with the abstract base class case, where the base class
doesn’t exist in its own right).
So a child model does not have access to its parent’s Meta class.
However, there are a few limited cases where the child inherits
behavior from the parent: if the child does not specify an ordering
attribute or a get_latest_by attribute, it will inherit these from its
parent.
If the parent has an ordering and you don’t want the child to have any
natural ordering, you can explicitly disable it:
class ChildModel(ParentModel):
# ...
class Meta:
# Remove parent's ordering effect
ordering = []
When an abstract base class is created, Django makes any Meta inner
class you declared in the base class available as an attribute. If a
child class does not declare its own Meta class, it will inherit the
parent’s Meta. If the child wants to extend the parent’s Meta class,
it can subclass it. For example:
from django.db import models
class CommonInfo(models.Model):
# ...
class Meta:
abstract = True
ordering = ['name']
class Student(CommonInfo):
# ...
class Meta(CommonInfo.Meta):
db_table = 'student_info'
I am not familiar with Wagtail, but can you take a look at this issue :
https://github.com/wagtail/wagtail/issues/4477#issuecomment-382277375
Update:
Maybe you just need to update your Person model like this:
class Person(models.Model):
...
class Meta:
ordering = 'name'
In your files, you try to order RelatedPeople by Person, but what you need is to order the Person list by name in your wagtail dropdown
Related
Suppose there are total 3 class. A,B and C.
class A(models.Model):
one = models.IntegerField()
two = models.IntegerField()
three = models.IntegerField()
class Meta:
abstract = True
class B(A):
pass
class C(A):
pass
I am inheriting the class A in B and C,but i want to use only fields one and two in classB while all the three fields in classC.
Is it possible to inherit some fields of classA in classB and some in classC?
or is it a bad idea?
As you may already know, there are three types of inheritance across models in django.
Often, you will just want to use the parent class to hold information that you don’t want to have to type out for each child model. This class isn’t going to ever be used in isolation, so Abstract base classes are what you’re after.
If you’re subclassing an existing model (perhaps something from another application entirely) and want each model to have its own database table, Multi-table inheritance is the way to go.
Finally, if you only want to modify the Python-level behavior of a model, without changing the models fields in any way, you can use Proxy models.
The only choice for your use-case is abstract base classes.
And the thing you are looking for from docs:
Fields inherited from abstract base classes can be overridden with another field or value, or be removed with None.
So you should have:
class A(models.Model):
one = models.IntegerField()
two = models.IntegerField()
three = models.IntegerField()
class Meta:
abstract = True
class B(A):
three = None
class C(A):
three = None
And to answer your second question, It's not a bad idea; We normally use it when we want to change the USERNAME_FIELD while extending django's default user model.
I use Django 1.10. I have the following model structure:
class GenericPage(models.Model):
"""Abstract page, other pages inherit from it."""
book = models.ForeignKey('Book', on_delete=models.CASCADE)
class Meta:
abstract = True
class GenericColorPage(models.Model):
"""Abstract page that is sketchable and colorable, other pages inherit from it."""
sketched = models.BooleanField(default=False)
colored = models.BooleanField(default=False)
class Meta:
abstract = True
class GenericBookPage(GenericColorPage):
"""A normal book page, with a number. Needs to be storyboarded and edited."""
###
#various additional fields
###
class Meta:
# unique_together = (('page_number', 'book'),) # impedes movement of pages
ordering = ('-book', '-page_number',)
abstract = True
objects = BookPageManager() # the manager for book pages
class BookPage(GenericBookPage):
"""Just a regular book page with text (that needs to be proofread)"""
proofread = models.BooleanField(default=False)
Additionally, an excerpt from Admin:
class BookPageAdmin(admin.ModelAdmin):
# fields NOT to show in Edit Page.
list_display = ('__str__', 'page_name', 'sketched', 'colored', 'edited', 'proofread',)
list_filter = ('book',)
readonly_fields = ('page_number',) # valid page number is assigned via overridden save() in model
actions = ['delete_selected',]
I tried to do ./manage.py makemigrations but if throws the following errors:
<class 'progress.admin.BookPageAdmin'>: (admin.E116) The value of 'list_filter[0]' refers to 'book', which does not refer to a Field.
progress.BookPage: (models.E015) 'ordering' refers to the non-existent field 'book'.
In the past, when I did not use the abstracts and just put everything into BookPage model, it all worked fine. But it seems that Meta and Admin don't see the fields in parent classes. Am I missing something? Is there a way to make them read fields from abstract parents?
In the past, when I did not use the abstracts and just put everything into BookPage model, it all worked fine
Of course it worked fine because you put everything inside BookPage which is not an abstract class which means that table (and, thus, fields) will be created.
But it seems that Meta and Admin don't see the fields in parent classes. Am I missing something?
You're missing the fact that none of your models inherits from the GenericPage abstract model. Thus, the book field is never created.
Is there a way to make them read fields from abstract parents?
You must create/modify a model that inherits from an abstract model. Maybe, do this:
class GenericBookPage(GenericColorPage, GenericPage):
which allows you to inherit both GenericColorPage and GenericPage fields. When I say inherit I mean when the migrate command runs to actually create the database table and the relevant columns (model fields).
I am adding a slug to all my models for serialization purposes, so I have defined an abstract base class which uses the AutoSlugField from django_autoslug.
class SluggerModel(models.Model):
slug = AutoSlugField(unique=True, db_index=False)
class Meta:
abstract=True
I also have a custom manager and a natural_key method defined, and at this point I have about 20 child classes, so there are several things that make using an abstract base model worthwhile besides just the single line that defines the field.
However, I want to be able to switch a few of the default arguments for initializing the AutoSlugField for some of the child models, while still being able to utilize the abstract base class. For example, I'd like some of them to utilize the populate_from option, specifiying fields from their specific model, and others to have db_index=True instead of my default (False).
I started trying to do this with a custom Metaclass, utilizing custom options defined in each child Model's inner Meta class, but thats become a rat's nest. I'm open to guidance on that approach, or any other suggestions.
One solution would be to dynamically construct your abstract base class. For example:
def get_slugger_model(**slug_kwargs):
defaults = {
'unique': True,
'db_index': False
}
defaults.update(slug_kwargs)
class MySluggerModel(models.Model):
slug = AutoSlugField(**defaults)
class Meta:
abstract = True
return MySluggerModel
class MyModel(get_slugger_model()):
pass
class MyModel2(get_slugger_model(populate_from='name')):
name = models.CharField(max_length=20)
Update: I started out with the following solution, which was ugly, and switched to Daniel's solution, which is not. I'm leaving mine here for reference.
Here's my Metaclass rat trap that seems to be working (without extensive testing yet).
class SluggerMetaclass(ModelBase):
"""
Metaclass hack that provides for being able to define 'slug_from' and
'slug_db_index' in the Meta inner class of children of SluggerModel in order to set
those properties on the AutoSlugField
"""
def __new__(cls, name, bases, attrs):
# We don't want to add this to the SluggerModel class itself, only its children
if name != 'SluggerModel' and SluggerModel in bases:
_Meta = attrs.get('Meta', None)
if _Meta and hasattr(_Meta, 'slug_from') or hasattr(_Meta, 'slug_db_index'):
attrs['slug'] = AutoSlugField(
populate_from=getattr(_Meta, 'slug_from', None),
db_index=getattr(_Meta, 'slug_db_index', False),
unique=True
)
try:
# ModelBase will reject unknown stuff in Meta, so clear it out before calling super
delattr(_Meta, 'slug_from')
except AttributeError:
pass
try:
delattr(_Meta, 'slug_db_index')
except AttributeError:
pass
else:
attrs['slug'] = AutoSlugField(unique=True, db_index = False) # default
return super(SlugSerializableMetaclass, cls).__new__(cls, name, bases, attrs)
The SlugModel looks basically like this now:
class SluggerModel(models.Model):
__metaclass__ = SluggerMetaclass
objects = SluggerManager()
# I don't define the AutoSlugField here because the metaclass will add it to the child class.
class Meta:
abstract = True
And I can acheive the desired effect with:
class SomeModel(SluggerModel, BaseModel):
name = CharField(...)
class Meta:
slug_from = 'name'
slug_db_index = True
I have to put SluggerModel first in the inheritance list for models having more than one abstract parent model, or else the fields aren't picked up by the other parent models and validation fails; however, I couldn't decipher why.
I guess I could put this an answer to my own question, since it works, but I'm hoping for a better way since its a bit on the ugly side. Then again, hax is hax so what can you do, so maybe this is the answer.
I'm trying to create a Django ORM mapping that's compatible with an existing data model, so I'm trying to work with an existing set of table and column names.
I've got a multi-table inheritance situation where a class InformationObject derives from class Object. I'd like to let Django handle this the usual way:
class Object(models.Model):
class Meta:
db_table = "object"
class InformationObject(Object):
class Meta:
db_table = "information_object"
In this case Django would automatically create a one-to-one field on the inheriting model called object_ptr_id. However, on the schema I'm constrained to use, the reference to the Object is simply called "id". So:
Is there a way to somehow specify the name of the column Django auto-magically uses for multi-table inheritance?
The alternative, which I'll have to use otherwise, is to use an explicit one-to-one field, but then I won't be able to inherit non-database methods from the Object model:
class Object(models.Model):
class Meta:
db_table = "object"
class InformationObject(models.Model):
class Meta:
db_table = "information_object"
id = models.OneToOneField(Object, primary_key=True, db_column="id")
Any ideas? Maybe I could create a common base class for both of them and put non-db methods there...?
From the django docs (development version):
As mentioned, Django will automatically create a OneToOneField linking your child class back any non-abstract parent models. If you want to control the name of the attribute linking back to the parent, you can create your own OneToOneField and set parent_link=True to indicate that your field is the link back to the parent class.
As mentioned by #fusion quoting from the docs, you will have to create a OneToOneField if you want to specify the column, while using model inheritance. The inherited fields will be available in the child class in both self scope AND the one-to-one field.
class Object(models.Model):
class Meta:
db_table = "object"
column_1 = models.CharField()
class InformationObject(Object):
class Meta:
db_table = "information_object"
# arbitrary property name (parent_link)
parent_link = models.OneToOneField(Object, primary_key=True, db_column="id", parent_link=True)
In this example:
>>> inf_obj = InformationObject.objects.get(pk=1)
>>> print inf_obj.column_1 == inf_obj.parent_link.column_1
True
Given the following models:(don't mind the TextFields there're just for illustration)
class Base(models.Model):
field1 = models.TextField()
class Meta:
abstract=True
class Child1(Base):
child1_field = models.TextField()
class Child2(Base):
child2_field = models.TextField()
class Content(models.Model):
aso_items = models.ManyToManyField('Base')
According to these definitions a Content object can be associated with more than one Base object, eg. an interview(=Content object) can be linked with a musician(=Child1 object), a filmdirector(=Child2), etc.
Now, for my question:
Is it possible to filter Content objects according to which model the aso_items field points to?
An example : Say I would like a Queryset containing all the Content objects that are associated with a specific object of Child1(eg. all the interviews associated with the musician Bob Dylan), how can I achieve this?
Further, what if I'd want a QuerySet containing all the Content objects that are associated with Child1 objects?(eg. all the interviews that associated with musicians)
How does this change the filtering?
Thanks in advance
ps: I'm experiencing some problems with white space in the preview, forgive me
You should check the section of the Django docs regarding using related_name for abstract base classes. http://docs.djangoproject.com/en/dev/topics/db/models/#be-careful-with-related-name
To quote the docs:
If you are using the related_name
attribute on a ForeignKey or
ManyToManyField, you must always
specify a unique reverse name for the
field. This would normally cause a
problem in abstract base classes,
since the fields on this class are
included into each of the child
classes, with exactly the same values
for the attributes (including
related_name) each time.
To work around this problem, when you
are using related_name in an abstract
base class (only), part of the name
should be the string %(class)s. This
is replaced by the lower-cased name of
the child class that the field is used
in. Since each class has a different
name, each related name will end up
being different.
Using this information I would recommend moving the m2m field into the Base class:
class Content(models.Model):
# Add remaining fields for Content
pass
class Base(models.Model):
field1 = models.TextField()
items = models.ManyToManyField(Content,related_name="%(class)s_related")
class Meta:
abstract=True
class Child1(Base):
child1_field = models.TextField()
class Child2(Base):
child2_field = models.TextField()
Apparently a ForeignKey relation(or ManyToMany for that matter) with a abstract class isn't allowed.
I get the following error : 'AssertionError: ForeignKey cannot define a relation with abstract class Artiest'.
A possible solution is to define the base class as non-abstract, however this implies that one could instantiate models of the base class. Which isn't the behavior I want.(after all it was an abstract class)
Has someone come accross the same problem how did you solve it? Any alternatives?
Have a look at http://www.djangoproject.com/documentation/models/generic_relations/ which goes through generic relations. Your Content model would match up to their TaggedItem model, and your Base model would match up to their Animal/Vegetable/Mineral model (with Child1 and Child2 extending).
Getting all of the Content objects for a single child would be (assuming you set the GenericRelation to contents inside Base):
child_contents = childObject.contents.all()
And to get all Content objects for a model:
ctype = ContentType.objects.get_for_model(Child1)
all_child_contents = Content.objects.filter(content_type=ctype)