How to show Many-to-Many Field on Django Admin Panel? - python

I have 2 models, Product and Tag. The relation between product and tag is Many-to-Many Relationship.
How to show "tags" field on django admin panel? Currently the value is None when I am using the code below
models.py
class Tag(models.Model):
name = models.CharField(max_length=200, null=True)
def __str__(self):
return self.name
class Product(models.Model):
CATEGORY = (
('Indoor','Indoor'),
('Outdoor','Outdoor'),
)
name = models.CharField(max_length=200, null=True)
price = models.FloatField(null=True)
category = models.CharField(max_length=200, choices=CATEGORY)
description = models.CharField(max_length=200, null=True)
date_created = models.DateTimeField(auto_now_add=True, null=True)
tags = models.ManyToManyField(Tag)
admin.py
#admin.register(Product)
class ProductAdmin(admin.ModelAdmin):
list_display = ['id','name','price','category','tags']
list_display_links = ['name']
def tags(self):
return self.tags.name

try this
#admin.register(Product)
class ProductAdmin(admin.ModelAdmin):
list_display = ['id', 'name', 'price', 'category', 'get_tags']
list_display_links = ['name']
def get_tags(self, obj):
if obj.tags.all():
return list(obj.tags.all().values_list('name', flat=True))
else:
return 'NA'
Refer this https://docs.djangoproject.com/en/3.1/ref/contrib/admin/#django.contrib.admin.ModelAdmin.list_display

#admin.register(Product)
class ProductAdmin(admin.ModelAdmin):
list_display = ['id','name','price','category','get_tags']
list_display_links = ['name']
def get_tags(self, instance):
return [tag.name for tag in instance.tags.all()]

In Django 4.1 you don't need to pass the second param (instance of class), and can do smth like this:
#admin.register(Product)
class ProductAdmin(admin.ModelAdmin):
list_display = ['id','name','price','category','get_tags']
list_display_links = ['name']
def get_tags(self):
return [tag.name for tag in instance.tags.all()]
Also, you can detalize this field, and add decorator #admin.display example for add title for django admin. More details in docs: https://docs.djangoproject.com/en/4.1/ref/contrib/admin/#django.contrib.admin.ModelAdmin.list_display

Related

an Inline class isnt working in django admin

I created a ProductAttributes model that have a ForeignKey from Product model
now i'm trying to create an admin panel for adding product using django admin
i'm adding ProductAttributes to Product admin with TabularInline but its not working
this the models and admin classes
#models
class Product(models.Model):
title = models.CharField(max_length=255)
slug = models.SlugField()
description = models.TextField(null=True, blank=True)
introduction = models.TextField(null=True, blank=True)
unit_price = models.DecimalField(
max_digits=12,
decimal_places=2,
validators=[MinValueValidator(1)])
inventory = models.IntegerField(validators=[MinValueValidator(0)])
last_update = models.DateTimeField(auto_now=True)
collection = models.ForeignKey(Collection, on_delete=models.PROTECT, related_name='products')
promotions = models.ManyToManyField(Promotion, blank=True)
def __str__(self) -> str:
return self.title
class Meta:
ordering = ['title']
class ProductAttributes(models.Model):
Product = models.ForeignKey(Product, on_delete=models.CASCADE, related_name="attributes")
attribute = models.CharField(max_length=255)
#admin
class ProductAttributesInline(admin.TabularInline):
model = models.ProductAttributes
#admin.register(models.Product)
class ProductAdmin(admin.ModelAdmin):
autocomplete_fields = ['collection']
prepopulated_fields = {
'slug': ['title']
}
actions = ['clear_inventory']
inlines = [ProductAttributesInline]
list_display = ['title', 'unit_price',
'inventory_status', 'collection_title']
list_editable = ['unit_price']
list_filter = ['collection', 'last_update', InventoryFilter]
list_per_page = 10
list_select_related = ['collection']
search_fields = ['title']
def collection_title(self, product):
return product.collection.title
#admin.display(ordering='inventory')
def inventory_status(self, product):
if product.inventory < 10:
return 'Low'
return 'OK'
#admin.action(description='Clear inventory')
def clear_inventory(self, request, queryset):
updated_count = queryset.update(inventory=0)
self.message_user(
request,
f'{updated_count} products were successfully updated.',
messages.ERROR
)
class Media:
css = {
'all': ['store/style.css']
}
the ProductAttributes isnt shown in Product admin
in the orginal project i created another inline for ProductImage and its working but when i try to delete that inline its not gone from product admin
Firstly, do not forget checking all migrations, and It would be more good to keep your models in models.py and do not mix them with admin related changes. I would recommend you to write them in admin.py.
You can use both images and attributes like that:
class ProductAttributesInlineAdmin(admin.TabularInline):
model = ProductAttributes
extra = 2
#admin.register(models.Product)
class ProductAdmin(admin.ModelAdmin):
...
inlines = [ProductAttributesInlineAdmin, ProductImageInlineAdmin]

DRF Add annotated field to nested serializer

I have two serializers that represent comments and their nested comments. I'm provide a queryset to viewset with annotated field likes. But my problem is that field only working in parent serializer. When i add this field to nested serializer i got error
Got AttributeError when attempting to get a value for field likes on serializer CommentChildrenSerializer.
The serializer field might be named incorrectly and not match any attribute or key on the Comment instance.
Original exception text was: 'Comment' object has no attribute 'likes'.
Here is some my code. Thanks
Models.py
class Post(models.Model):
author = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
title = models.CharField(max_length=200)
slug = models.SlugField(blank=True)
body = models.TextField()
tags = TaggableManager(blank=True)
pub_date = models.DateTimeField(auto_now_add=True)
class Meta:
ordering = ['-pub_date']
class Comment(models.Model):
post = models.ForeignKey(Post, on_delete=models.CASCADE,
related_name='comments')
author = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
text = models.TextField(max_length=500)
pub_date = models.DateTimeField(auto_now=True)
parent = models.ForeignKey('self', blank=True, null=True,
on_delete=models.CASCADE, related_name='children')
class Meta:
ordering = ['-pub_date']
class Vote(models.Model):
comment = models.ForeignKey(Comment, on_delete=models.CASCADE,
related_name='votes')
user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
choice = models.BooleanField(null=True)
Serializers.py
class PostRetrieveSerializer(PostSerializer):
comments = CommentSerializer(read_only=True, many=True)
author = AuthorInfoSerializer(serializers.ModelSerializer)
class Meta:
model = Post
fields = ['id', 'author', 'slug', 'title', 'body', 'tags', 'pub_date', 'comments']
class CommentChildrenSerializer(serializers.ModelSerializer):
author = AuthorInfoSerializer(read_only=True)
likes = serializers.IntegerField()
class Meta:
model = Comment
fields = ['author', 'id', 'text', 'pub_date', 'parent', 'likes']
class CommentSerializer(serializers.ModelSerializer):
author = AuthorInfoSerializer(read_only=True)
children = CommentChildrenSerializer(many=True)
likes = serializers.IntegerField()
class Meta:
ordering = ['pub_date']
model = Comment
fields = ['author', 'id', 'text', 'pub_date', 'children', 'likes']
Views.py
class PostViewSet(viewsets.ModelViewSet):
queryset = Post.objects.all().prefetch_related(
Prefetch('comments', queryset=Comment.objects.filter(parent__isnull=True)
.annotate(likes=Count('votes__choice'))))
serializer_class = PostSerializer
permission_classes = [IsOwnerOrAdminOrReadOnly]
pagination_class = PostPagination
lookup_field = 'slug'
def get_serializer_class(self):
""""
Attach related comments
when get post detail
"""
if self.action == 'retrieve':
return PostRetrieveSerializer
return self.serializer_class
def perform_create(self, serializer):
serializer.save(author=self.request.user)
maybe you can do something like this, adding the like field in each child comment.
queryset = Post.objects.all()\
.prefetch_related(
Prefetch(
'comments',
queryset=Comment.objects\
.filter(parent__isnull=True)\
.annotate(likes=Count('votes__choice'))\
.prefetch_related(
'children',
queryset=Comments.objects.all()\
.annotate(likes=Count('votes__choice'))
)
)
)
I hope this help you.
Regards!
On your model level, add a custom property like the below.
class Comment(models.Model):
...
class Meta:
ordering = ['-pub_date']
#property
def likes(self):
return self.votes.count()
On your serializer add SerializerMethodField
class CommentChildrenSerializer(serializers.ModelSerializer):
author = AuthorInfoSerializer(read_only=True)
likes = serializers.SerializerMethodField() # Change here
class Meta:
model = Comment
fields = ['author', 'id', 'text', 'pub_date', 'parent', 'likes']
# method for the SerializerMethodField
def get_likes(self, obj):
return obj.likes
Update both Comment related serializers. I believe this approach is simpler than the current implementation.

Am having troubles with django-parler 2.0.1 after i had applied migration to translations, it won't show Products fields in admin site

this is my setting for translations in the models.py file, django-parler 2.0.1 won't show fields for Products in the admin site after I had synch migrations. I am currently using Django 3.0.3.
from django.db import models
from django.urls import reverse
from parler.models import TranslatableModel, TranslatedFields
class Category(TranslatableModel):
translations = TranslatedFields(
name = models.CharField(max_length=200,
db_index=True),
slug = models.SlugField(max_length=200,
db_index=True,
unique=True)
)
class Meta:
# ordering = ('name',)
verbose_name = 'category'
verbose_name_plural = 'categories'
def __str__(self):
return self.name
def get_absolute_url(self):
return reverse('shop:product_list_by_category',
args=[self.slug])
class Product(TranslatableModel):
translations = TranslatedFields(
name = models.CharField(max_length=200, db_index=True),
slug = models.SlugField(max_length=200, db_index=True),
description = models.TextField(blank=True)
)
category = models.ForeignKey(Category,
related_name='products',
on_delete=models.CASCADE)
image = models.ImageField(upload_to='products/%Y/%m/%d',
blank=True)
price = models.DecimalField(max_digits=10, decimal_places=2)
available = models.BooleanField(default=True)
created = models.DateTimeField(auto_now_add=True)
updated = models.DateTimeField(auto_now=True)
#class Meta:
# ordering = ('name',)
# index_together = (('id', 'slug'),)
def __str__(self):
return self.name
def get_absolute_url(self):
return reverse('shop:product_detail',
args=[self.id, self.slug])
I have registered the model in the admin.py file but it won't show the fields for product description and price all I get is the translated tab.
from django.contrib import admin
from .models import Category, Product
from parler.admin import TranslatableAdmin
#admin.register(Category)
class CategoryAdmin(TranslatableAdmin):
list_display = ['name', 'slug']
def get_prepopulated_fields(self, request, obj=None):
return {'slug': ('name',)}
#admin.register(Product)
class ProductAdmin(TranslatableAdmin):
list_display = ['name', 'slug', 'price',
'available', 'created', 'updated']
list_filter = ['available', 'created', 'updated']
list_editable = ['price', 'available']
def get_prepopulated_fields(self, request, obj=None):
return {'slug': ('name',)}
I wonder what am doing wrong that I am getting this and I wonder if there's a better way to make translate configurations with Django-parler 2.0.1. any suggestions is welcomed!!
Apparently, I was able to fix the problem by first deleting all migrations and I also had to trash my database and switch to PostgreSQL and after that, I reapplied migrations and it worked. All fields are now visible in the admin site now yippee! but be mindful to make a backup of your database because everything will be deleted..

Django call 'id' expected a number but got string

Django errors with django-import-export libraries.
I want to import data from excel to db via django admin. I use for it django-import-export, but i got Field 'id' expected a number but got 'HPI'.
Excel file contains
I found answer, that I have to add exclude = ('id',), but it didn't help. Also i did migrations, it didn't help too.
How to fix it and have ability to import 6 columns data from excel to db via django admin?
models.py
from django_mysql.models import JSONField, Model
from django.db import models
class Category(Model):
title = models.CharField(max_length=100)
class Meta:
ordering = ('-id',)
verbose_name = 'Category'
verbose_name_plural = 'Categories'
def __str__(self):
return self.title
class Tag(Model):
title = models.CharField(max_length=100)
class Meta:
ordering = ('-id',)
def __str__(self):
return self.title
class Type(Model):
title = models.CharField(max_length=100)
class Meta:
ordering = ('-id',)
verbose_name = 'Type'
verbose_name_plural = 'Types'
def __str__(self):
return self.title
class Macro(Model):
type = models.ForeignKey(
Type,
max_length=100,
null=True,
blank=True,
on_delete=models.SET_NULL)
tags = models.ManyToManyField(Tag, blank=True)
category = models.ForeignKey(
Category, null=True, blank=True, on_delete=models.SET_NULL)
abbreviation = models.CharField(max_length=100, unique=True)
title = models.CharField(max_length=100, verbose_name='Title')
content = models.TextField(max_length=1000, null=True, blank=True)
class Meta:
ordering = ('-id',)
def __str__(self):
return self.title
admin.py
from django.contrib import admin
from import_export import resources
from import_export.admin import ImportExportModelAdmin
from .models import Category, Tag, Type, Macro
class MacroResource(resources.ModelResource):
class Meta:
model = Macro
skip_unchanged = True
report_skipped = True
exclude = ('id', )
export_order = ('type', 'tags', 'category', 'abbreviation', 'title', 'content')
#admin.register(Macro)
class MacroAdmin(ImportExportModelAdmin):
resource_class = MacroResource
list_display = ('id', 'type', 'tags_list', 'category', 'abbreviation', 'title', 'content')
search_fields = ('title', 'category__title', 'type__title', 'abbreviation', 'content', )
def tags_list(self, obj):
tags = [t for t in obj.tags.all()]
return ' '.join(str(tags)) if tags else '-'
#admin.register(Category)
class CategoryAdmin(admin.ModelAdmin):
list_display = ('id', 'title')
#admin.register(Tag)
class TagAdmin(admin.ModelAdmin):
list_display = ('id', 'title')
def __str__(self):
return self.title
#admin.register(Type)
class TypeAdmin(admin.ModelAdmin):
list_display = ('id', 'title')
The problem was with ForeignKey and ManyToMany fields of a database model. So django-import-export library need to get widgets for this fields.
More about it here: https://django-import-export.readthedocs.io/en/latest/api_widgets.html#import_export.widgets.ForeignKeyWidget
Solution:
admin.py
class MacroResource(resources.ModelResource):
type = fields.Field(
column_name='type',
attribute='type',
widget=ForeignKeyWidget(Type, 'title'))
category = fields.Field(
column_name='category',
attribute='category',
widget=ForeignKeyWidget(Category, 'title'))
tags = fields.Field(
column_name='tags',
attribute='tags',
widget=ManyToManyWidget(Tag, field='title'))
class Meta:
model = Macro
skip_unchanged = True
report_skipped = True
exclude = ('id', )
import_id_fields = ('title',)
fields = ('type', 'tags', 'category', 'abbreviation', 'title', 'content')
instead of
class MacroResource(resources.ModelResource):
class Meta:
model = Macro
skip_unchanged = True
report_skipped = True
exclude = ('id', )
export_order = ('type', 'tags', 'category', 'abbreviation', 'title', 'content')
Django-import-export expects the first column to be id.
If these are new objects, simply leave the id column blank. Otherwise, put the database id of the object in that field.
If you're not able to modify the file, or don't want to, and you will always be adding new rows to the database (not modifying existing ones), you can create an id field dynamically in your resource class by overriding the method before_import and forcing get_instance to always return False.
class MacroResource(resources.ModelResource):
def before_import(self, dataset, using_transactions, dry_run, **kwargs):
dataset.insert_col(0, col=["",]*dataset.height, header="id")
def get_instance(self, instance_loader, row):
return False
class Meta:
model = Macro
skip_unchanged = True
report_skipped = True
export_order = ('type', 'tags', 'category', 'abbreviation', 'title', 'content')
The attributes where you are using a foreign key in the model,
you need to specify the id of the parent model and not the value in the xlsx/csv file.

Get sub-key's count in admin list-filter

Here is my models.py:
class Category(models.Model):
category = models.CharField(max_length=50, verbose_name=u"文章分类", default="")
add_time = models.DateTimeField(default=datetime.now, verbose_name=u"添加时间")
class Meta:
verbose_name = u"文章分类"
verbose_name_plural = verbose_name
def __unicode__(self):
return self.category
class Article(models.Model):
category = models.ForeignKey(Category, verbose_name=u"文章分类")
title = models.CharField(max_length=100, verbose_name=u"文章题目", default="")
content = UEditorField(width=1000, height=500,imagePath="media/", filePath="media/",verbose_name=u"文章内容")
read_nums = models.IntegerField(default=0, verbose_name=u"阅读量")
comment_nums = models.IntegerField(default=0, verbose_name=u"评论数")
add_time = models.DateTimeField(default=datetime.now, verbose_name=u"添加时间")
class Meta:
verbose_name = u"文章"
verbose_name_plural = verbose_name
def __unicode__(self):
return self.title
Now I need to show how many articles in category at django-admin page, in this page:
How should I do?
You can add custom columns via the list_display of your admin class. There you can just add a method that you add to your model (or the admin) and that returns the desired value:
class Category(models.Model):
# ...
def article_count(self):
return self.article_set.count()
article_count.short_description = 'Number of articles' # displayed as label/column header
class CategoryAdmin(admin.ModelAdmin):
readonly_fields = ['article_count'] # this is your custom method
list_display = ['__str__', 'article_count', 'add_time']
admin.site.register(Category, CategoryAdmin)

Categories