Django call 'id' expected a number but got string - python

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.

Related

Django rest framework - using SerializerMethodField with ModelSerializer

I have the following models.
models.py
class Language(models.Model):
name = models.CharField(max_length=255, unique=True)
class Subject(models.Model):
name = models.CharField(max_length=255, unique=True)
class Term(models.Model):
subject = models.ForeignKey(Subject, on_delete=models.CASCADE)
language = models.ForeignKey(Language, on_delete=models.CASCADE)
name = models.CharField(max_length=255)
definition = models.TextField()
image = models.ImageField(default='', blank=True)
I want to write a Serializer that will return a list of subjects. Each subject has a term field, which contains None or a single object, depending on the condition.
serializers.py
class TermSerializer(serializers.ModelSerializer):
language = serializers.CharField(source='language.name')
def to_representation(self, data):
data = data.get(language=self.context['language'])
return super(TermSerializer, self).to_representation(data)
class Meta:
model = Term
fields = ('id', 'language', 'name', 'definition', 'image')
class FilteredSubjectSerializer(serializers.ListSerializer):
def to_representation(self, data):
if self.context['condition']:
terms = Term.objects.filter(language=self.context['language'])
data = data.filter(term__in=terms)
return super(FilteredSubjectSerializer, self).to_representation(data)
class SubjectSerializer(serializers.ModelSerializer):
term = serializers.SerializerMethodField()
def get_term(self, term):
if self.context['condition']:
return TermSerializer(many=False, source='term_set').data
return None
class Meta:
model = Term
list_serializer_class = FilteredSubjectSerializer
fields = ('id', 'name', 'definition', 'image', 'term')
The problem is that when condition == True, the ViewSet returns incorrect data. All fields inside term have a default value.
It works fine if I write SubjectSerializer like this:
class SubjectSerializer(serializers.ModelSerializer):
term = serializers.TermSerializer(many=False, source='term_set')
class Meta:
model = Term
list_serializer_class = FilteredSubjectSerializer
fields = ('id', 'name', 'definition', 'image', 'term')
But the case when condition == False doesn't work.
Try passing the serializer the instance and the context.
Like this:
def get_term(self, instance):
if self.context['condition']:
return TermSerializer(many=False, source='term_set', instance=instance, context=self.context).data
return None

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.

ManyToManyField does not show

Want to use REST API to populate my tables but my field does not display on the API page.
Models (Series, Roster):
class Series(models.Model):
(...)
def __str__(self):
return self.title
class Roster(models.Model):
(...)
series = models.ManyToManyField(Series)
(...)
def __str__(self):
return self.name
Serializers:
class SeriesSerializer(serializers.ModelSerializer):
class Meta:
model = Series
fields = ('id', 'title', 'icon')
read_only_fields = ('slug',)
class RosterSerializer(serializers.ModelSerializer):
series = SeriesSerializer(many=True, read_only=True)
class Meta:
model = Roster
fields = ('id', 'name', 'number', 'primary_color', 'secondary_color', 'image', 'series')
Views:
class SeriesView(viewsets.ModelViewSet):
serializer_class = SeriesSerializer
queryset = Series.objects.all()
class RosterView(viewsets.ModelViewSet):
serializer_class = RosterSerializer
queryset = Roster.objects.all()
Unsure where I am mistepping here.
So it turns out that all I needed to do was remove
series = SeriesSerializer(many=True, read_only=True)
and adjust my series field to
series = models.ForeignKey(Series, on_delete=models.CASCADE, blank=True, null=True)
No idea why this ended up working though so an explanation would still be cool.

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

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

django rest framework nested relationships -

I have some trouble understanding Django Rest Framework. I am building an application, very simple. And for some reason I don't understand I am not able to make it work properly.
I am using:
python: 3.4.0
Django: 1.7.4
Django Rest Framework: 3.0.5
Here is the thing:
#models.py
class Country(StandardMetadata, GeoBase):
CONTINENT_CHOICES = (
('OC', 'Oceania'),
('EU', 'Europe'),
('AF', 'Africa'),
('NA', 'North America'),
('AN', 'Antarctica'),
('SA', 'South America'),
('AS', 'Asia'),
)
name = models.CharField(max_length=200, db_index=True)
slug = AutoSlugField(populate_from='name')
continent = models.CharField(max_length=2, choices=CONTINENT_CHOICES, default='NA')
iso2 = models.CharField(max_length=2)
iso3 = models.CharField(max_length=3)
def __str__(self):
return self.name
class Meta:
verbose_name_plural = 'Countries'
class Region(StandardMetadata, GeoBase):
name = models.CharField(max_length=200, db_index=True)
slug = AutoSlugField(populate_from='name')
iso2 = models.CharField(max_length=2)
country = models.ForeignKey(Country)
def __str__(self):
return self.get_full_name()
def get_full_name(self):
return ', '.join([self.name, self.country.name])
class Meta:
unique_together = ('slug', 'country')
verbose_name = 'Region/State/Province'
verbose_name_plural = 'Regions/States/Provinces'
#serializers.py
class CountrySerializer(serializers.ModelSerializer):
class Meta:
model = Country
fields = ('id', 'name', 'slug', 'iso2', 'iso3', 'continent', 'lng', 'lat')
class RegionSerializer(serializers.ModelSerializer):
country = CountrySerializer(read_only=True, required=True, many=False)
class Meta:
model = Region
fields = ('id', 'name', 'slug', 'iso2', 'lng', 'lat', 'country',)
#views.py
class CountryViewSet(viewsets.ModelViewSet):
queryset = Country.objects.all()
serializer_class = CountrySerializer
class RegionViewSet(viewsets.ModelViewSet):
queryset = Region.objects.all()
serializer_class = RegionSerializer
#urls.py
router = routers.DefaultRouter()
router.register(r'countries', CountryViewSet)
router.register(r'regions', RegionViewSet)
So for some reason I got this error when I try to access to this url ( http://192.168.33.30:5000/regions/ ): May not set both read_only and required
What am I missing ?
Thanks a lot for your help !
EDIT
I found one of my problem. I removed the line unique_together = ('slug', 'country') in my Region model. I set the slug field as unique. The second problem is that I had to break my model to make it work.
Rather than specify read-only in the Serializer, do this:
class RegionSerializer(serializers.ModelSerializer):
country = CountrySerializer(required=True, many=False)
class Meta:
model = Region
fields = ('id', 'name', 'slug', 'iso2', 'lng', 'lat', 'country',)
read_only_fields = ('country', )

Categories