create Django ModelAdmin classes dynamically - python

I have a whole bunch of models of 3 different types (I have 3 abstract base models, let's say Bread, Eggs, and Beer) and lots of models that inherit one of those three).
I would like to register the concrete models with the admin differently based on their abstract base class, without having to write a separate ModelAdmin class for each one (ie I want to write only 3 ModelAdmin classes - one for each abstract base class). Each type of concrete model has a set of fields that I want displayed (they are different on different concrete models, but in a well-defined way, and I can get a list of the fields I want displayed on an Egg model easily). Example:
#abstract base class for Egg
class Egg(models.Model):
fields = ...
def get_only_concrete_fields(self):
'Return all fields defined on a child of Egg that are not defined on Egg'
return concrete_fields
class Meta:
abstract = True
#concrete class
class WhiteEgg(Egg):
morefields = ...
#concrete class
class BrownEgg(Egg):
otherfields = ...
So what I want to do is register both WhiteEgg and BrownEgg in the admin (without having to write a separate modeladmin class for each), and I want to set list_display to the result of get_only_concrete_fields(), plus a few (not all) hand-selected fields from Egg. I know their is a get_list_display() (see https://stackoverflow.com/a/16115125/710394) but I don't think it gets the model, so I can't call get_only_concrete_fields().
I have also tried looping over the models like below, defining list_display for each, and registering each model with a "base" ModelAdmin, but that doesn't work because it just reassigns that modeladmin in each iteration of the loop.
for models in all_egg_models:
modeladmin = MyModelAdmin #or deepcopy(MyModelAdmin) -also doesn't work
fieldnames = ...the fields i want...
setattr(modeladmin, 'list_display', fieldnames)
admin.site.register(model, modeladmin)
I'm stuck - how can I do this, and DRY?

Your idea of overriding the get_list_display method sounds good.
In the method, you can access the model with self.model.

Related

How to set related_name in ManyToMany field in an abstract model?

I have this abstract model:
class HasSystemMessage(models.Model):
class Meta:
abstract = True
messages = models.ManyToManyField(SystemMessage, related_name=?)
I am going to use this abstract model in at least three other models, lets say, A, B, and C. How can I set the related_name dynamically for these classes? for example, for class B, I want the related_name to be Bs. Is it possible to do so?
To further clarify the question, The classes will look like this:
class B(HasSystemMessage):
# Some model fields
class A(HasSystemMessage):
# Some model fields
HasSystemMessage.objects.filter(a__contains=[some elements])
You can use %(class)s or %(app_label)s
class HasSystemMessage(models.Model):
class Meta:
abstract = True
messages = models.ManyToManyField(SystemMessage, related_name=%(app_label)s_%(class)s_related)
From Django docs
Be careful with related_name and related_query_name¶ If you are using
related_name or related_query_name on a ForeignKey or ManyToManyField,
you must always specify a unique reverse name and query 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 and related_query_name) each time.
To work around this problem, when you are using related_name or
related_query_name in an abstract base class (only), part of the value
should contain '%(app_label)s' and '%(class)s'.
'%(class)s' is replaced by the lower-cased name of the child class
that the field is used in. '%(app_label)s' is replaced by the
lower-cased name of the app the child class is contained within. Each
installed application name must be unique and the model class names
within each app must also be unique, therefore the resulting name will
end up being different.
Ref: https://docs.djangoproject.com/en/2.0/topics/db/models/#be-careful-with-related-name-and-related-query-name
You Just need to put a string in this attribute which specifies the name of the reverse relation from the SystemMessage.Also read in Django Docs
Try this:
class HasSystemMessage(models.Model):
class Meta:
abstract = True
messages = models.ManyToManyField(SystemMessage, related_name='system_message')

Why "class Meta" is necessary while creating a model form?

from django import forms
from .models import NewsSignUp
class NewsSignUpForm(forms.ModelForm):
class Meta:
model = NewsSignUp
fields = ['email', 'first_name']
here
This code works perfectly fine. But, when I remove "class Meta:" as below, it throws a ValueError saying "ModelForm has no model class specified."
from django import forms
from .models import NewsSignUp
class NewsSignUpForm(forms.ModelForm):
model = NewsSignUp
fields = ['email', 'first_name']
Can someone please give an explanation? :(
You are creating a ModelForm subclass. A model form has to have a model to work from, and the Meta object configures this.
Configuration like this is grouped into the Meta class to avoid name clashes; that way you can have a model field in your form without that interfering with the configuration. In other words, by using class Meta: you get a nested namespace used just to configure the ModelForm in relation to the model.
The namespace for the ModelForm class body itself then (outside Meta) is reserved for the form fields themselves, as well as form methods. You'd normally just let ModelForm generate those fields from your model, but you can, in principle, add fields to this. Another reason to put fields in the class is to completely replace any of the generated fields with your own version.
From the Model Forms documentation:
ModelForm is a regular Form which can automatically generate certain fields. The fields that are automatically generated depend on the content of the Meta class and on which fields have already been defined declaratively. Basically, ModelForm will only generate fields that are missing from the form, or in other words, fields that weren’t defined declaratively.
Fields defined declaratively are left as-is, therefore any customizations made to Meta attributes such as widgets, labels, help_texts, or error_messages are ignored; these only apply to fields that are generated automatically.
Because ModelForm expects the configuration to be set under the Meta name, you can't just remove that and put model and fields in the ModelForm class itself; that's just the wrong place.

DJANGO models proxy inharitence

I would like to add the methods of the 'tools' class for 2 of my django model calss.
Each class will use the same methods with it's own model eample:
class mapA(models.Model):
mInd = models.IntegerField()
scId = models.IntegerField()
class mapB(models.Model):
mInd = models.IntegerField()
scId2 = models.IntegerField()
I would like to add the methods like checkInput() to both of them.
So I could run:
mapBInstance.checkInput();
mapAInstance.checkInput();
Ech time the checkInput runs over the data in the mapA or mapB.
I thought about creating a tools class & let each model to inherit from it.
This way the tools class will have logic which is identical to both maps.
When I read the django docs I didn't see example to this case only close solutions.
Is this the correct solution (to use the proxy class)?
class Tools():
def __init__():
...init class...
def checkInput():
..make the checks..
class MapA(Tools, models.Model):
mInd = models.IntegerField()
scId = models.IntegerField()
def checkSelf():
self.checkInput(self.objects.filter(....))
class MapB(Tools, models.Model):
mIndB = models.IntegerField()
scIdB = models.IntegerField()
def checkSelf():
self.checkInput(self.objects.filter(....))
A few things...
There is no this in Python, it's called self.
If you're in Python 2.x, tools should inherit from object. In Python 3, it's implicit, but doesn't hurt:
class tools(object):
...
If you're overriding __init__ in your mixin class (tools), then map classes should probably inherit from it first:
class mapA(tools, models.Model):
...
Only override __init__ if you really need to, it can get complicated.
Class names are pretty much always in CamelCase. This is not required, but is a convention. Also, It's a good idea to name mixin classes transparently:
class ToolsMixin(object):
...
class MapA(ToolsMixin, models.Model):
...
Other then all that, you perfectly can add a method in a mixin and use it in your models. No need for Django proxy models.
If you want MapA and MapB (it would be really helpful if you followed PEP-8) to be distinct models, proxy models won't help you. A proxy model is a model that is different in Python, but in the database it is exactly the same as the model it inherits from. Creating a proxy model that doesn't directly inherit from a single concrete model (one that has a table in the database) is an error.
What you're looking for is an abstract base class:
class Tools(models.Model):
...
class Meta:
abstract = True
class MapA(Tools):
...
class MapB(Tools):
...
An abstract model does not create its own table in the database. Instead, it is as if everything defined in Tools has been defined in both MapA and MapB, but the Tools class is otherwise ignored. This allows you to specify all the methods and fields just once, but still have two separate tables in the database.

class definition inside a class?

class ItemForm(djangoforms.ModelForm):
class Meta:
model = Item
exclude = ['added_by']
i can not understand what this piece of code is doing .i understood that ItemForm is inheriting Modelform but then a class definition inside a class ??
The Item class is :
class Item(db.Model):
name = db.StringProperty()
quantity = db.IntegerProperty(default=1)
target_price = db.FloatProperty()
priority = db.StringProperty(default='Medium',choices=[
'High', 'Medium', 'Low'])
entry_time = db.DateTimeProperty(auto_now_add=True)
added_by = db.UserProperty()
It's part of Django's magic. The metaclass for ModelForm (among other classes) looks for an inner Meta class and uses it to make various changes to the outer class. It's one of the deeper parts of Python that most people will never have to deal with first-hand.
In Python you can define classes within other classes as a way of encapsulating the inner class. The way Django is using this is actually quite excellent.
See this link for more info: http://www.geekinterview.com/question_details/64739
Meta is a special class definition.
In this example, it is a simple inheritance model. ModelForm creates a form based on a Model class, so via giving a class definiton to ModelForm class, it creates the form elements according to related Model class definiton.

Can't use an inheriting Django model's Meta class to configure a field defined in an inherited abstract model

I would like to use properties from an inheriting model's Meta class to configure a field defined in an abstract model higher up the inheritance tree:
class NamedModel(models.Model):
class Meta:
abstract = True
verbose_name = 'object'
name = models.CharField("Name",
max_length=200,
db_index=True,
help_text="A meaningful name for this %s." % Meta.verbose_name)
# see what I'm trying to do here?
)
...
class OwnedModel(NamedModel):
class Meta(NamedModel.Meta):
verbose_name = 'owned object'
I would like the help text on the name field of OwnedModel forms to say 'A meaningful name for this owned object'. But it does not: the word 'owned' is missing, which would suggest that the verbose_name from the NamedModel.Meta is used when the model is set up, not OwnedModel.Meta.
This isn't quite what I expect from an inheritance point of view: is there some way to get the field to be created whereby Meta.verbose_name refers to the value on the non-abstract model class, not the abstract one on which the field was defined?
Or am I being daft?
(This may seem like a trivial example, and it is: but it's just to illustrate the point of something more important and complex I am trying to do)
Many thanks in advance.
Why don't you try to make a class.
class BaseNamedModelMeta:
abstract = True
verbose_name = "your text"
And then inherit and override whatever you want like this:
class OwnedModel(NamedModel):
class Meta(BaseNamedModelMeta):
verbose_name = 'owned object'
I think this happens because Meta.verbose_name is used and NamedModel.name is created when class NamedModel is parsed. So later, when class OwnedModel gets parsed, there is no chance to change anything.
Maybe you can set the help_text property on OwnedModel.name later on, but this may change NamedModel.name also.
In similar situations I have put the variable parts in class attribute of the model (not Meta) and then used the by run time methods/properties to generate the texts I need.
In fact I ended up doing the following. The base model gets given a dynamic_field_definition() class method, which can be used to patch up the fields, with the cls argument being the correct (inheriting) class. That means that that cls' Meta attributes are of that correct child, not the original base.
I then wire up that method to get called on the class_prepared signal, so that you know everything's otherwise ready.
class NamedModel(models.Model):
...
#classmethod
def dynamic_field_definition(cls):
pass
def dynamic_field_definition(sender, **kwargs):
if issubclass(sender, NamedModel):
sender.dynamic_field_definition()
class_prepared.connect(dynamic_field_definition)
Then the field properties that vary with model class are simply reconfigured by that class method (or more likely the method as overridden in derived classes).
It's a slightly hacky way to bring a last little bit of OO-ness to Django models, but it works fine for my purpose.

Categories

Resources