I have written a base class for admin classes for all of my classes that have a field called is_active. It enables the user to see the records of the table along with the is_active field so that they can see which record is active or not and they can edit it.
For most of the classes that I have in my models, there is a field called name so I can easily write 2 lines of the code in admin.py and have a customized admin representation, but the problem is for the classes that don't have a field called name so I have to use a different field in list_display . Since the number of these classes is high, I am looking for a solution to either dynamically get the name of the field that must be in the list_display. Any ideas to solve this problem would be much appreciated.
models.py:
class BaseAdmin(admin.ModelAdmin):
list_display=('id','name','is_active')
list_editable = ('is_active',) # this MUST only contain fields that also are in "list_display"
search_fields=('name',)
class ClassA(models.Model):
name=models.CharField(max_length=20, blank=False, unique=True,)
is_active=models.BooleanField(default=True,)
def __str__(self):
return self.name
class ClassB(models.Model):
my_field=models.CharField(max_length=20, blank=False, unique=True,)
is_active=models.BooleanField(default=True,)
def __str__(self):
return self.my_field
admin.py
class ClassAAdmin(BaseAdmin):
pass
class ClassBAdmin(BaseAdmin):
pass
You are looking for get_list_display https://docs.djangoproject.com/en/1.11/ref/contrib/admin/#django.contrib.admin.ModelAdmin.get_list_display
Use that in conjunction with ModelAdmin.model (i.e. self.model inside the get_list_display method) to create a dynamic list display based on the model.
Note that I would personally recommend not baking this automation into the BaseAdmin, and defining what is different between the subclass ModelAdmins (in this case, list_display) separately. It's more explicit and clear. This is pure opinion though.
Related
I need extend a model from another model.
Case:
core/models.py
class Master(models.Model):
code = models.CharField(max_length=30, unique=True)
name = models.CharField(max_length=100, blank=False, null=False)
class Meta:
abstract = True
class City(Master):
zipcode = models.IntegerField()
custom/models.py
from core.models import City
class City(City)
newfield = models.CharField(max_length=20)
custom is an app.
I have tried with proxy model but it is not what I need, since proxy model adds a new table. https://docs.djangoproject.com/en/2.2/topics/db/models/#proxy-models
I need is that when I migrate add the new field to City.
More info.
In core the table is created and in custom you can add new fields that the client needs. The idea is that core is only maintained as standard.
Proxy models don't add new tables. From the docs link you mentioned:
The MyPerson class operates on the same database table as its parent Person class.
If you want one table called core_city, and another called custom_city, the second one having an extra field, you simply subclass it. Perhaps it would be easier to use an alias:
from core.models import City as CoreCity
class City(CoreCity):
newfield = models.CharField(max_length=20)
custom_city will have all fields from core_city, plus a newfield. The description of how this works (and an example) is covered in the docs section Multi-table inheritance.
If what you want is to have one single database table, then you should use a proxy Model, however they really don't allow you to create new fields. The field should be created in the parent model, or otherwise exist in the database and not be handled by Django migrations at all.
You are looking for Abstract base classes models:
Abstract base classes are useful when you want to put some common information into a number of other models. You write your base class and put abstract=True in the Meta class.
This is the base class:
#core/models.py
class City(Master):
zipcode = models.IntegerField()
class Meta:
abstract = True # <--- here the trick
Here your model:
#custom/models.py
from core.models import City as CoreCity
class City(CoreCity):
newfield = models.CharField(max_length=20)
For many uses, this type of model inheritance will be exactly what you want. It provides a way to factor out common information at the Python level, while still only creating one database table per child model at the database level.
You can update or create your class constants after its defined like this
from core.models import City
City.newfield = models.CharField(max_length=20)
You may need to use swappable models, using them you can define a City class and change it with whichever model you need later,
but that way you can't import and use the base City model directly, you may need to provide a method like get_city_model for that, as your public API.
class City(Master):
zipcode = models.IntegerField()
class Meta:
swappable = 'CORE_CITY_MODEL'
and maybe replace it later with some other model, then just set CORE_CITY_MODEL to that model in the form of 'app_name.model_name'.
The django.contrib.auth is a good example of this, you may consider checking User model and get_user_model method. Although I think you may face problems if you change your city model after you did run migrate, it may not move your data to the new table, but I'm not sure about this.
I just started working with generic class based views and love it. I do have one struggle that I still can't solve.
I have one abstract class called group, and two childs Company and Bond. (I simplified my example). This is my models.py:
models.py
class Group(BaseModel):
name = models.CharField(max_length=30, unique=True)
class Meta:
abstract = True
class Company(Group):
def __str__(self):
return "Company " + self.name
class Bond(Group):
def __str__(self):
return "Bond " + self.name
Now when displaying one of my objects with the generic class based detail view, I want to have one group_detail.html template, and call group.name. This doesn't work however, I need to call company.name or bond.name, depending on the object type. Since my original model has a lot more attributes, this is undesirable because it results in a lot of duplicate code. Do you guys know a workaround for this?
Group has no instances in the database as it's an abstract class so you can't query it. You have to query Company or Bond and so pass Company or Bond objects to a template.
If your Company and Bond models have the same attributes you can pass their object to a template with a single context name, this will let you use one template. But, actually, I think your Company and Bond models are different so you can't create one template for both of them, they will differ, so there is no code duplication at all.
Just use the same template for both views, and use the automatically passed object variable to the context, like {{ object.name }}.
I'm currently experimenting with model mixins, the idea being to build a small library of small abstract mixin classes defining commonly needed fields.
Here's what i'm currently playing with:
class HtmlAttrsMixin(models.Model):
css_classes = models.CharField(
max_length=256,
verbose_name=_('CSS Classes'),
blank=True,
)
class Meta:
abstract = True
class LinkHtmlAttrsMixin(HtmlAttrsMixin):
target_blank = models.BooleanField(
default=False,
verbose_name=_('Open in a new window /tab'),
)
title = models.CharField(
max_length=512,
verbose_name=_('Title'),
blank=True,
)
class Meta:
abstract = True
class URLMixin(models.Model):
label = models.CharField(
max_length=256,
verbose_name=_('Name'),
blank=True,
)
url = models.CharField(
max_length=4000,
verbose_name=_('URL'),
blank=True,
)
class Meta:
abstract = True
# Concrete model implementing the above mixins:
class TagLine(URLMixin, LinkHtmlAttrsMixin):
enabled = models.BooleanField(_("enabled"), default=True)
def __unicode__(self):
return self.label
This is working fine so far, but there's a little something i don't quite understand.
I'd like to be able to kind of decide of the inherited fields' ordering by simply declaring the mixins in a different order. As far as i know, the default field ordering is based on the order in which those were declared on the model class, and python will resolve attribute names by going through the base classes in the order in which they were listed, so in the above example, i'd expect the css_classes, target_blank & title fields to be listed AFTER label & url in the admin site, and vice versa if i invert the order of the two mixins in the declaration.
But no matter how i list the mixins, the "html_attrs" fields keep appearing first. They are grouped together (which seems logical, since they belong to the same class), but i just can't seem to force the ordering in that way.
I know this is a trivial question - I can just fix that with an admin fieldset, which will prove much more flexible anyway. It just seemed like a convenient trick which i expected to work, so i'm simply interested in understanding why it doesn't.
(Also, if anyone has any advice about model mixins in general, i'm all ears - I've found some info, but not much, and google for django models mixins tends to return lots of results about CBV mixins, which is not what i'm interested in at the moment).
Thanks to anyone who'll care to answer!
This doesn't answer your question, but I do think the approach is really nice.
Reordering fields in Django model
For forms:
How does Django Know the Order to Render Form Fields?
Instead of using a fields attribute in your form to be edited every time you add a new field to your Model class, make a logic to add the exception where you want it.
Another approach: you can use from django.forms import fields_for_model, and create a function to populate your fields attribute using this function. Check the documentation for this method, is really nice!
I'm working on my first real Django project after years of PHP programming, and I am running into a problem with my models. First, I noticed that I was copying and pasting code between the models, and being a diligent OO programmer I decided to make a parent class that the other models could inherit from:
class Common(model.Model):
name = models.CharField(max_length=255)
date_created = models.DateTimeField(auto_now_add=True)
date_modified = models.DateTimeField(auto_now=True)
def __unicode__(self):
return self.name
class Meta:
abstract=True
So far so good. Now all my other models extend "Common" and have names and dates like I want. However, I have a class for "Categories" were the name has to be unique. I assume there should be a relatively simple way for me to access the name attribute from Common and make it unique. However, the different methods I have tried to use have all failed. For example:
class Category(Common):
def __init__(self, *args, **kwargs):
self.name.unique=True
Causes the Django admin page to spit up the error "Caught an exception while rendering: 'Category' object has no attribute 'name'
Can someone point me in the right direction?
No, Django doesn't allow that.
See the docs: http://docs.djangoproject.com/en/1.1/topics/db/models/#field-name-hiding-is-not-permitted
Also answered in other questions like: In Django - Model Inheritance - Does it allow you to override a parent model's attribute?
You have a small mistake in your Common class
class Common(model.Model):
self.name = models.CharField(max_length=255)
should be
class Common(model.Model):
name = models.CharField(max_length=255)
Note that UNIQUE constraint in fact has nothing to do with Django, so you're free to add it in your database table. You can also use post-syncdb hook for that purpose.
Try using Meta.unique_together to force it into its own unique index. Failing that, it's probably easiest to create two separate abstract classes, one with the field unique and one not.
Assume the following:
models.py
class Entry(models.Model):
title = models.CharField(max_length=50)
slug = models.CharField(max_length=50, unique=True)
body = models.CharField(max_length=200)
admin.py
class EntryAdmin(admin.ModelAdmin):
prepopulated_fields = {'slug':('title',)}
I want the slug to be pre-populated by the title, but I dont want the user to be able to edit it from the admin. I assumed that adding the fields=[] to the admin object and not including the slug would have worked, but it didnt. I also tried setting editable=False in the model, but that also didnt work (infact, stops the page from rendering).
Thoughts?
For this particular case you can override your save method to slugify (it's built-in method, look at django source) the title and store it in slug field. Also from there you can easily check if this slug is indeed unique and change it somehow if it's not.
Consider this example:
def save(self):
from django.template.defaultfilters import slugify
if not self.slug:
self.slug = slugify(self.title)
super(Your_Model_Name,self).save()
I'm not sure what you're asking for IS possible. Your best bet is probably to hide the slug from the admin interface completely by specifying your fieldsets, and than overriding the save method to copy the slug from the tile, and potentially slugifying it...
This Django Snippet does what you want by defining a custom Read-Only Widget. So you define a custom editor for the field which in fact doesn't allow any editing.
This snippet gives you an AutoSlugField with exactly the behavior you are seeking, and adding it to your model is a one-liner.
In addition to overriding save to provide the generated value you want, you can also use the exclude option in your ModelAdmin class to prevent the field from being displayed in the admin:
class EntryAdmin(admin.ModelAdmin):
exclude = ('slug',)