Lets say I have a three tier model as below, where posts are in categories which are then in categories. I'd like to get the Posts page in the Admin panel to display both the sub category and category that each post belongs to. And ideally let me filter by both. So far I can only get the Admin panel to work up one relationship level. So I can display and filter by the sub_category but when I add the category I get an error.
models.py:
class Category(models.Model):
name=models.CharField(max_length=10)
def __str__(self):
return self.name
class Subcategory(models.Model):
category=models.ForeignKey(Category,on_delete=models.CASCADE,null=True)
name=models.CharField(max_length=10)
def __str__(self):
return self.name
class Posts(models.Model):
title=models.CharField(max_length=15)
sub_category=models.ForeignKey(Subcategory,on_delete=models.CASCADE,null=True)
def __str__(self):
return self.title
Admin.py:
class PostsAdmin(admin.ModelAdmin):
ordering = ('name',)
list_display = ('name','id','sub_category', 'sub_category__category.name', )
exclude = ('sort',)
list_filter = ['sub_category','sub_category__category']
search_fields = ['name']
admin.site.register(Posts, PostsAdmin)
Error is something like this:
<class 'journal.admin.PostsAdmin'>: (admin.E108) The value of 'list_display[4]' refers to 'sub_category__category', which is not a callable, an attribute of 'PostsAdmin', or an attribute or method on 'journal.Admin'.
You need to add a method on your admin class that returns the category for each post then reference this in list_display
class PostsAdmin(admin.ModelAdmin):
list_display = ('name','id','sub_category', 'category')
def category(self, obj):
return obj.sub_category.category.name
You could also add this method to the post model rather than the model admin
Related
These are my models:
class Partner(models.Model):
name = models.CharField(max_length=200, verbose_name="Organisation name")
class ResearchActivity(models.Model):
title = models.CharField(max_length=200)
partner = models.ManyToManyField(ActivityPartner, blank=True)
I'd like, in the Django administration forms, to have a field in my Partner edit form representing the ResearchActivity linked to that Partner.
Can this be achieved by adding a field to my Partner model (say, naming it linked_partner) and then edit my admin.py like so:
#admin.register(ActivityPartner)
class ActivityPartnerAdmin(admin.ModelAdmin):
search_fields = ['academic',]
autocomplete_fields = ['partnership_type', 'relationship_type', 'academic_links']
def get_changeform_initial_data(self, request):
return {'live_contract': ResearchActivity.objects.all().filter(linked_partner__id=request.ResearchActivity.partner.id)}
?
I have just come across in the display() decorator, new from Django 3.2. With it, I can simply do:
#admin.register(ActivityPartner)
class ActivityPartnerAdmin(admin.ModelAdmin):
search_fields = ['academic',]
autocomplete_fields = ['partnership_type', 'relationship_type', 'academic_links',]
readonly_fields = ('get_ra',)
#admin.display(description='Live contract(s)')
def get_ra(self, obj):
return list(ResearchActivity.objects.filter(partner=obj.id))
to achieve what I want.
If I also wanted to edit those ManyToMany relations, I can use the inlines option:
class LiveContractsInline(admin.TabularInline):
model = ResearchActivity.partner.through
#admin.register(ActivityPartner)
class ActivityPartnerAdmin(admin.ModelAdmin):
inlines = [
LiveContractsInline,
]
I just learned django last January and I'm currently working on website to store and show lyrics. My problem is that in createview the fields are not sorted.
Here is my models.py:
class Singer(models.Model):
song_singer = models.CharField(max_length=200)
def __str__(self):
return self.song_singer
def get_absolute_url(self):
return reverse('singer_author')
class Lyrics(models.Model):
title = models.CharField(max_length=50)
singer = models.ForeignKey(Singer, on_delete=models.RESTRICT)
type = models.ForeignKey(Type, on_delete=models.RESTRICT)
lyrics = models.TextField()
Views.py:
class AddSongView(LoginRequiredMixin, CreateView):
model = Lyrics
fields = ['title', 'singer', 'type', 'lyrics']
Screenshot in the browser
As you can see in the attached screenshot, the choices from singer field is not sorted. How to sort those choices? Thank you!
The easiest way to do this is by defining a default ordering=… [Django-doc] on your Singer model, this will then order the items by name if you do not specify another ordering:
class Singer(models.Model):
# …
class Meta:
ordering = ['song_singer']
Best to use a ModelForm here. This also gives you the chance to modify your form even more:
from django import forms
class SongForm(forms.ModelForm):
singer = forms.ModelChoiceField(queryset=Singer.objects.all().order_by('song_singer'))
class Meta:
model = Lyrics
fields = ['title', 'singer', 'type', 'lyrics']
class AddSongView(LoginRequiredMixin, CreateView):
form_class = SongForm
I have a many to many field like this:
class Retailer(models.Model):
name = models.CharField(max_length=100, db_index=True)
city_gps = models.ManyToManyField(City, blank=True, related_name='retailers', db_index=True)
def __str__(self):
retailer_city = ""
if self.city_gps:
retailer_city = self.city_gps.all().first().name
return slugify(self.name) + slugify(retailer_city)
I would like the admin to show a combination of the name and all related cities. However, when I set the admin to show this field like this:
class RetailerAdmin(admin.ModelAdmin):
search_fields = ['name']
list_display = ['name', 'city_gps', 'icon_tag', 'logo_tag', 'header_tag']
I get the error:
: (admin.E109) The value of
'list_display[1]' must not be a ManyToManyField.
So, how can I solve this? Is there a way to show the value of the __str__ method in the the admin?
As said in the docs for list_display in Django:
ManyToManyField fields aren’t supported, because that would entail executing a separate SQL statement for each row in the table. If you want to do this nonetheless, give your model a custom method, and add that method’s name to list_display. (See below for more on custom methods in list_display.)
So you can define this custom method either in your models.py or (I think the more explicit way), directly in your admin.py:
class RetailerAdmin(admin.ModelAdmin):
search_fields = ['name']
list_display = ['name', 'icon_tag', 'logo_tag', 'header_tag', 'retailer_city']
def retailer_city(self, obj):
city_gps = obj.city_gps
retailer_city = city_gps.all().first().name if city_gps else ''
return slugify(obj.name) + slugify(retailer_city)
Note that retailer_city is added in list_display.
I have two models:
class ProductCategory(models.Model):
'''
Product Category determines to which category the product falls into.
'''
category_name = models.CharField(max_length=254)
def __unicode__(self):
return u"%s" %(self.category_name)
class Meta:
verbose_name_plural = "Product Categories"
class Product(models.Model):
'''
Product Information
'''
name = models.CharField(max_length=250)
category = models.ForeignKey(ProductCategory)
def __unicode__(self):
return u"%s" %(self.product_name)
I want to apply autocomplete on category field of product model. Thus,
class ProductCategoryAutoComplete(autocomplete_light.AutocompleteModelBase):
search_fields = ['category_name']
model = Product
choices = ProductCategory.objects.all()
autocomplete_light.register(Product, ProductCategoryAutoComplete)
I have included the template too. Everything works fine. Except when I choose the category and submit the form html field is required is popping up in the bottom. What is wrong?
Edit: Form
class ProductCreateForm(autocomplete_light.ModelForm):
category = forms.ModelChoiceField(queryset=ProductCategory.objects, widget=autocomplete_light.ChoiceWidget('ProductCategoryAutoComplete'))
class Meta:
model = Product
Oops !
Product.category is an FK to model Category, but the Autocomplete you are passing (ProductCategoryAutoComplete) is registered for Product model ! Field that should allow selecting a Category should use an autocomplete for Category, not one for Product ;)
Better usage of autocomplete_light.ModelForm
Since you're using autocomplete_light.ModelForm, you don't have to specify the field. Just register an autocomplete for Category:
autocomplete_light.register(Category, search_fields=['category_name'])
And let autocomplete_light.ModelForm do the field definition:
class ProductCreateForm(autocomplete_light.ModelForm):
class Meta:
model = Product
Yes, that's all you need ;)
search_fields is wrong, product does`n have field category_name, if I understand your idea it have to be:
class ProductCategoryAutoComplete(autocomplete_light.AutocompleteModelBase):
search_fields = ['category__category_name']
model = Product
choices = ProductCategory.objects.all()
autocomplete_light.register(Product, ProductCategoryAutoComplete)
I want to display the level field of the category to which the product is related on the object's admin page.
class Category(models.Model):
name = models.CharField(max_length=50, default=False)
level = models.IntegerField(help_text="1, 2 ,3 or 4")
class Product(models.Model):
category = models.ForeignKey(Category)
name = models.CharField(max_length=100)
prepopulated_fields = {'slug': ('name',)}
fieldsets = [
('Product Info',{'fields': ['name', 'slug','partno','description']}),
('Categorisation',{'fields': ['brand','category']}),
I have found references to list_filter, but nothing regarding how to show the field.
Does anyone know how to do this?
Define a method on the ModelAdmin class which returns the value of the related field, and include that in list_display.
class ProductAdmin(admin.ModelAdmin):
list_display = ('name', 'level')
model = Product
def level(self, obj):
return obj.category.level
To show the related field in a ModelAdmin's fieldset, the field must first be declared in readonly_fields.
Define a method that returns the desired value.
Include the method or its name in readonly_fields.
Include the method or its name in its fieldset's "fields" list.
from django.contrib import admin
from .models import MyModel
#admin.register(MyModel)
class MyModelAdmin(admin.ModelAdmin):
readonly_fields = ['get_parent_name'] # Don't forget this!
fieldsets = [('Parent info', {'fields': ['get_parent_name']} )]
#admin.display(description='Parent')
def get_parent_name(self, obj):
return obj.parent.name
On the Change page, there will be a "Parent info" section with the object's parent's name.
In your admin.py file
class ProductAdmin(admin.ModelAdmin):
list_display = ('name', 'category__level', 'category')
admin.site.register(Product, ProductAdmin)
Try this.............
The simplest way is to put the level of the Category into the __unicode__ method:
class Category(models.Model):
name = models.CharField(max_length=50, default=False)
level = models.IntegerField(help_text="1, 2 ,3 or 4")
def __unicode__(self):
return u'%s [%d]' % (self.name, self.level)
So the select box will show it.