I wanted to list all the reviews along with the replies to specific review of specific restaurant but i could only list the review. While trying to fetch all the replies to its specific review, i called the children() which is defined in Review model. It returned queryset. On the other hand i want all the specific replies with the name of replier.
serializers.py
class ReviewSeraializer(ModelSerializer):
reply_count = SerializerMethodField()
children = SerializerMethodField()
class Meta:
model = Review
read_only = ('id',)
fields = ('id','content_type','object_id','parent','review','children','reply_count','created')
def get_reply_count(self, obj):
if obj.is_parent:
return obj.children().count()
return 0
def get_children(self, obj):
obj_children = []
if obj.is_parent:
return str(obj.children())
# for obj in obj.children():
# print(obj.review)
# obj_children.append(obj.review)
# return str(obj_children)
return None
review/models.py
class Review(models.Model):
reviewer = models.ForeignKey(User, null=True)
content_type = models.ForeignKey(ContentType, on_delete=models.CASCADE)
object_id = models.PositiveIntegerField()
content_object = GenericForeignKey('content_type', 'object_id')
parent = models.ForeignKey("self", null=True, blank=True, related_name="parent_review")
review = models.TextField()
created = models.DateTimeField(auto_now_add=True)
updated = models.DateTimeField(auto_now=True)
objects = ReviewManager()
def children(self): # replies
return Review.objects.filter(parent=self)
#property
def is_parent(self):
if self.parent is not None:
return False
return True
How to list all the replies and replier name inside children key of a specific review?
Add another serializer that can serialize your children.
class ReviewSeraializerChild(serializers.ModelSerializer):
class Meta:
model = Review
class ReviewSeraializer(serializers.ModelSerializer):
reply_count = serializers.SerializerMethodField()
children = ReviewSeraializerChild(many=True)
class Meta:
model = Review
read_only = ('id',)
fields = ('id', 'content_type', 'object_id', 'parent', 'review', 'children', 'reply_count', 'created')
def get_reply_count(self, obj):
if obj.is_parent:
return obj.children().count()
return 0
But you must write your custom create() and update() functions for nested fields.
Related
I tried to make method(location_item) that shows item by region.
And I want to implement the 'gu, dong' field(in location model) with multiple choices.
so, wrote this method code. but filtering doesn't work..
This shows all item objects, not just the objects I want.
TypeError: Field 'id' expected a number but got <Item: 애니원모어 원피스입니다!>.
[13/Aug/2022 15:01:07] "GET /item_post/location/location_item/ HTTP/1.1" 500 152955
I don't know what sholud i do.
please help me...
models.py
class Item(models.Model):
user_id = models.ForeignKey(User, related_name='item_sets', on_delete=models.CASCADE)
category_id = models.ForeignKey(Category, related_name='item_sets', on_delete=models.DO_NOTHING)
description = models.TextField()
feature = models.TextField()
product_defect = models.TextField()
size = models.CharField(max_length=6)
wear_count = models.IntegerField()
price = models.IntegerField()
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
def __str__(self):
return self.description
class Location(models.Model):
city = models.CharField(max_length=10)
gu = models.CharField(max_length=10)
dong = models.CharField(max_length=10)
def __str__(self):
return self.city+" "+self.gu+" "+self.dong
class LocationSet(models.Model):
item_id = models.ForeignKey(Item, on_delete=models.CASCADE, related_name='location_sets')
location_id = models.ForeignKey(Location, on_delete=models.CASCADE, related_name='location_sets')
serializers.py
class ItemSerializer(serializers.ModelSerializer):
photos = PhotoSerializer(source='photo_sets', many=True, read_only=True)
style_photos = StylePhotoSerializer(source='style_photo_sets', many=True, read_only=True)
class Meta:
model = Item
fields = '__all__'
class LocationSerializer(serializers.ModelSerializer):
class Meta:
model = Location
fields = '__all__'
class LocationSetSerializer(serializers.ModelSerializer):
class Meta:
model = LocationSet
fields = '__all__'
views.py
class ItemViewSet(ModelViewSet):
queryset = Item.objects.all()
serializer_class = ItemSerializer
filter_backends = [SearchFilter, OrderingFilter]
search_fields = ['description'] # ?search=
ordering_fields = ['-created_at'] # ?ordering=
ordering = ['-created_at']
# here
#action(detail=False, methods=['GET'])
def location_item(self, request):
locations = Location.objects.all()
city = request.GET.get('city', None)
gu = request.GET.getlist('gu', None) # multiple choices
dong = request.GET.getlist('dong', None) # multiple choices
print(city, " ", gu, " ", dong) # > None [] [] (upper codes not work..)
if city:
locations = locations.filter(city=city)
if gu:
locations = locations.filter(gu__in=gu).distinct()
if dong:
locations = locations.filter(dong__in=dong).distinct()
location_ids = []
for i in locations:
location_ids.append(i.id)
locationsets = LocationSet.objects.filter(location_id__in=location_ids)
item_ids = []
for i in locationsets:
item_ids.append(i.item_id)
serializer = self.get_serializer(item_ids, many=True)
return Response(serializer.data, status=status.HTTP_200_OK)
To create method that shows items by region.
what sholud i do?
I think the last lines of code are wrong.
...
#action(detail=False, methods=['GET'])
def location_item(self, request):
...
# here I changed the last four lines
item_ids = [x.item_id for x in locationsets]
serializer = self.get_serializer(item_id__id__in = item_ids, many=True)
return Response(serializer.data, status=status.HTTP_200_OK)
I have nested serializer (AmountSerializer). I need a field meal_name in one ViewSet. But when this field is nested, I don't need it to be seen in endpoint(in MealSerializer). How to exclude field from nested serializer when is it actually nested?
models.py:
class MealType(models.Model):
name = models.TextField()
def __str__(self):
return self.name
class Ingredient(models.Model):
name = models.TextField()
def __str__(self):
return self.name
class Meal(models.Model):
name = models.TextField()
type = models.ForeignKey(MealType, on_delete=models.CASCADE, default=None)
recipe = models.TextField()
photo = models.ImageField(null=True, height_field=None, width_field=None, max_length=None,upload_to='media/')
ingredients = models.ManyToManyField(Ingredient)
def __str__(self):
return self.name
class IngredientAmount(models.Model):
ingredient_name = models.ForeignKey(Ingredient, on_delete=models.CASCADE, default=None)
amount = models.FloatField(default=None)
meal = models.ForeignKey(Meal, on_delete=models.CASCADE, default=None, related_name='meal_id')
class Meta:
ordering = ['meal']
def __str__(self):
return self.ingredient_name
serializers.py:
class AmountSerializer(serializers.ModelSerializer):
ingredient_name= serializers.ReadOnlyField(source='ingredient_name.name')
-->#meal_name = serializers.ReadOnlyField(source='meal.name')
#I CAN'T use ReadOnlyField( #with write_only=True)
#i trired use PrimaryKeyRelatedField
# butgot AssertionError: Relational field must provide a `queryset` argument, override `get_queryset`, or set read_only=`True`.
class Meta:
model = IngredientAmount
fields = ('ingredient_name','amount','meal_name')
class MealSerializer(serializers.ModelSerializer):
type_name= serializers.ReadOnlyField(source='type.name')
ingredients = serializers.SlugRelatedField(read_only=True, slug_field='name', many=True)
amount = AmountSerializer(read_only=True, many=True,source='meal_id')
class Meta:
model = Meal
fields = ('id', 'name', 'type_name', 'recipe', 'photo', 'ingredients','amount')
I'd rather use a trick to exclude some of the fields that are not needed in certain situations. You can inherit your serializer from ExcludeFieldsModelSerializer, and exclude any fields that you want so that the serializer will not serialize that field.
class ExcludeFieldsModelSerializer(serializers.ModelSerializer):
"""
A ModelSerializer that takes an additional `exclude_fields` argument that
controls which fields should be excluded from the serializer.
Plagiarised from https://www.django-rest-framework.org/api-guide/serializers/#dynamically-modifying-fields
"""
def __init__(self, *args, **kwargs):
# Don't pass the 'exclude_fields' arg up to the superclass
exclude_fields = kwargs.pop('exclude_fields', None)
# Instantiate the superclass normally
super(ExcludeFieldsModelSerializer, self).__init__(*args, **kwargs)
if exclude_fields is not None:
# Drop any fields that are specified in the `exclude_fields` argument.
drop = set(exclude_fields)
for field_name in drop:
self.fields.pop(field_name)
class AmountSerializer(ExcludeFieldsModelSerializer):
ingredient_name= serializers.ReadOnlyField(source='ingredient_name.name')
meal_name = serializers.CharField(read_only=True, source='meal.name')
class Meta:
model = IngredientAmount
fields = ('ingredient_name','amount','meal_name')
class MealSerializer(serializers.ModelSerializer):
type_name= serializers.ReadOnlyField(source='type.name')
ingredients = serializers.SlugRelatedField(read_only=True, slug_field='name', many=True)
amount = AmountSerializer(read_only=True, many=True, source='meal_id', exclude_fields={'meal_name'})
class Meta:
model = Meal
fields = ('id', 'name', 'type_name', 'recipe', 'photo', 'ingredients','amount')
So I'm wondering if it is possible to serialize each foreign key object with different serializer in django rest framework.
What I mean is:
I have my models like
class KingdomModel(models.Model):
kingdom_name = models.CharField(max_length=32)
owner = models.OneToOneField(User, on_delete=models.CASCADE)
faction = models.CharField(max_length=10)
class CityModel(models.Model):
kingdom = models.ForeignKey(KingdomModel, on_delete=models.CASCADE, related_name="cities")
city_name = models.CharField(max_length=32)
owner = models.ForeignKey(User, on_delete=models.CASCADE)
"""
... other fields aswell
"""
class ArmyModel(models.Model):
home_city = models.ForeignKey(CityModel, on_delete=models.CASCADE, null=True, related_name="own_troops")
current_city = models.ForeignKey(CityModel, on_delete=models.CASCADE, null=True, related_name="all_troops", blank=True)
status = models.CharField(max_length=32)
action_done_time = models.DateTimeField(default=None, null=True, blank=True)
target_city = models.ForeignKey(CityModel, on_delete=models.CASCADE, null=True, related_name="incoming_troops", default=None, blank=True)
# Shared troops
settlers = models.IntegerField(default=0)
# Gaul troops
pikemen = models.IntegerField(default=0)
swordmen = models.IntegerField(default=0)
riders = models.IntegerField(default=0)
# Roman troops
legionaries = models.IntegerField(default=0)
praetorian = models.IntegerField(default=0)
And I am trying to serialize the armies based on the kingdoms faction. Which works fine when talking about own_troops because they are always going to be serialized with the same serializer, like so.
class CitySerializer(serializers.ModelSerializer):
own_troops = serializers.SerializerMethodField()
incoming_troops = serializers.SerializerMethodField()
def get_own_troops(self, city_obj):
if(KingdomModel.objects.get(owner=city_obj.owner).faction == "Gaul"):
return GaulTroopsSerializer(instance=city_obj.own_troops, context=self.context, many=True, required=False, read_only=False).data
elif(KingdomModel.objects.get(owner=city_obj.owner).faction == "Roman"):
return RomanTroopsSerializer(instance=city_obj.own_troops, context=self.context, many=True, required=False, read_only=False).data
class RomanTroopsSerializer(serializers.ModelSerializer):
class Meta:
model = ArmyModel
fields = ['id', 'home_city', 'current_city', 'target_city', 'status', 'action_done_time', 'settlers', 'legionaries', 'praetorian']
class GaulTroopsSerializer(serializers.ModelSerializer):
class Meta:
model = ArmyModel
fields = ['id', 'home_city', 'current_city', 'target_city', 'status', 'action_done_time', 'settlers', 'pikemen', 'swordmen', 'riders']
But if I try to apply the same logic to serializing the incoming_troops, it will always serialize all of the objects in the list with the first serializer. This was my hopeless attempt at serializing each foreign key with different serializer based on the data inside the relation.
def get_incoming_troops(self, city_obj):
for data in GaulTroopsSerializer(instance=city_obj.incoming_troops, context=self.context, many=True, required=False, read_only=False).data:
print(data)
home_city_obj = CityModel.objects.get(id=data['home_city'])
if(KingdomModel.objects.get(owner=home_city_obj.owner).faction == "Gaul"):
return GaulTroopsSerializer(instance=city_obj.incoming_troops, context=self.context, many=True, required=False, read_only=False).data
else:
return RomanTroopsSerializer(instance=city_obj.incoming_troops, context=self.context, many=True, required=False, read_only=False).data
class Meta:
model = CityModel
fields = ['id', 'owner', 'city_name', 'x_coordinate', 'y_coordinate', 'last_updated', 'max_warehouse_capacity', 'max_grain_silo_capacity', 'wood_ammount', 'wheat_ammount', 'stone_ammount', 'iron_ammount', 'resource_fields', 'buildings','incoming_troops', 'own_troops', 'all_troops']
read_only_fields = ['id', 'max_warehouse_capacity', 'max_grain_silo_capacity']
I know I could just have multiple models for all of the different factions armies, but for now I am just wondering if this is possible in django / drf?
Answering my own question because I got it working and what I did is the following:
First of all I scraped the multiple troop serializers. And had just one army serializer where I switch the fields according to the faction.
This is my ArmySerializer now
class ArmySerializer(serializers.ModelSerializer):
class Meta:
model = ArmyModel
fields = ['id', 'home_city', 'current_city', 'target_city', 'status', 'action_done_time']
def to_representation(self, instance):
try:
del self.fields # Clear the cache
except AttributeError:
pass
if("faction" in self.context and len(self.context['faction']) > 0):
print(self.context['faction'])
self.fields['settlers'] = serializers.IntegerField()
if(self.context['faction'][0] == "Gaul"):
self.fields['pikemen'] = serializers.IntegerField()
self.fields['swordmen'] = serializers.IntegerField()
self.fields['riders'] = serializers.IntegerField()
elif(self.context['faction'][0] == "Roman"):
self.fields['legionaries'] = serializers.IntegerField()
self.fields['praetorian'] = serializers.IntegerField()
if(len(self.context['faction']) > 1):
self.context['faction'].pop(0)
return super().to_representation(instance)
And in CitySerializer I am populating the context['faction'] list like this:
class CitySerializer(serializers.ModelSerializer):
own_troops = serializers.SerializerMethodField()
incoming_troops = serializers.SerializerMethodField()
def get_own_troops(self, instance):
if(KingdomModel.objects.get(owner=instance.owner).faction == "Gaul"):
self.context["faction"] = ["Gaul"]
return ArmySerializer(instance=instance.own_troops, context=self.context, many=True, required=False, read_only=False).data
elif(KingdomModel.objects.get(owner=instance.owner).faction == "Roman"):
self.context["faction"] = ["Roman"]
return ArmySerializer(instance=instance.own_troops, context=self.context, many=True, required=False, read_only=False).data
def get_incoming_troops(self, city_obj):
self.context['faction'] = []
for data in ArmySerializer(instance=city_obj.incoming_troops, context=self.context, many=True, required=False, read_only=False).data:
home_city = CityModel.objects.get(id=data['home_city'])
sender_faction = KingdomModel.objects.get(owner=home_city.owner).faction
if(sender_faction == "Gaul"):
self.context['faction'] += ["Gaul"]
else:
self.context['faction'] += ["Roman"]
return ArmySerializer(instance=city_obj.incoming_troops, context=self.context, many=True, required=False, read_only=False).data
Also it should be said that, this introduced a new problem when creating an army with POST requests. The fields that are dynamically added in the to_representation method are not validated by default, so they are not present in validated_data. There might be a way to override validation but I just took them from the raw request data for now and it seems to be working fine.
So I have these models:
class Group(models.Model):
name = models.CharField(max_length=255, blank=True, null=True, default=None)
class Member(models.Model):
name = models.CharField(max_length=255, blank=True, null=True, default=None)
group = models.ForeignKey(Group, related_name='members', on_delete=models.CASCADE)
is_deleted = models.BooleanField()
And these serializers:
class GroupSerializer(serializers.ModelSerializer):
members = MemberSerializer(many=True, read_only=True)
class Meta:
model = Group
fields = '__all__'
class MemberSerializer(serializers.ModelSerializer):
# some field defintions here
class Meta:
model = Member
fields = '__all__'
And view:
class GroupViewSet(viewsets.ModelViewSet):
queryset = Group.objects.all()
serializer_class = GroupSerializer
Now what I want to do basically, is not return "members" of Group that has is_deleted set to True.
So if I have a Group with 3 Member where 1 has is_deleted set to True, I want my serializer to return the Group with 2 Members.
How do I go about achieving this?
To achieve this you can override the members field in the GroupSerializer to return a filtered queryset
class GroupSerializer(serializers.ModelSerializer):
members = serializers.SerializerMethodField()
def get_members(self, obj):
qs = Members.objects.filter(group=obj, is_deleted=True)
serialized = MemberSerializer(qs, many=True)
return serialized.data
class Meta:
model = Group
fields = '__all__'
This will return the member objects with is_deleted=True for the specific group.
Use serializer method field in members serializer
https://www.django-rest-framework.org/api-guide/fields/#serializermethodfield
You can modify the MemberSerializer which only return the is_deleted false field.
class MemberSerializer(serializers.ModelSerializer):
is_deleted_false = = serializers.SerializerMethodField(read_only=True)
#staticmethod
def get_is_deleted_false(obj):
if obj.is_deleted == False:
return obj.is_deleted
else:
pass
class Meta:
model = Member
fields = ('id', 'name', 'group', 'is_deleted_false')
I'm tring to create a generic foreign key and reuse it multiple time inside the same model
The problem is that when I load the admin interface I got the 2 Inline fields that display the same data.
I tried to use an fk_name passing the related_name defined in the model but without luck
Here is the code any help is appreciate
model.py:
class TranslatedCharField(models.Model):
content_type = models.ForeignKey(ContentType)
object_id = models.PositiveIntegerField()
content_object = generic.GenericForeignKey()
class Meta:
unique_together = ('object_id', 'lang')
lang = models.CharField(max_length=255, choices=LANGUAGES)
text = models.CharField(max_length=255)
class Evento(models.Model):
slug = generic.GenericRelation(TranslatedCharField, related_name="slug")
titolo = generic.GenericRelation(TranslatedCharField, related_name="title")
admin.py:
class TranslatedSlugInline(generic.GenericTabularInline):
extra = 0
model = TranslatedCharField
verbose_name = "Slug"
verbose_name_plural = "Slug"
class TranslatedTitoloInline(generic.GenericTabularInline):
extra = 1
model = TranslatedCharField
verbose_name = "Titolo"
verbose_name_plural = "Titolo"
class EventoAdmin(admin.ModelAdmin):
inlines = [
TranslatedSlugInline,
TranslatedTitoloInline,
]
admin.site.register(Evento, EventoAdmin)
this is the result:
as you can see the list of TranslatedCharField is duplicated for the 2 relations
unique_together = ('object_id', 'lang')
I think you want
unique_together = ('content_type', 'object_id', 'lang')
because object_id is not unique across models.
slug = generic.GenericRelation(TranslatedCharField, related_name="slug")
titolo = generic.GenericRelation(TranslatedCharField, related_name="title")
"related_name" is how the TranslatedCharField can access in the other direction, i.e. by which attribute name you want it to access that Evento. "slug_event" and "title_event" more fitting.
ok I'd reviewed the things a but and the result is the follow:
class TranslatedCharField(models.Model):
content_type = models.ForeignKey(ContentType)
object_id = models.PositiveIntegerField()
content_object = generic.GenericForeignKey()
relation_field = models.CharField(max_length=255)
class Meta:
unique_together = ('object_id', 'lang', 'relation_field')
lang = models.CharField(verbose_name="lingua", max_length=255, choices=LANGUAGES)
text = models.CharField(verbose_name="testo", max_length=255)
def translatedFieldInlineAdminFormFactory(relation_field, translatemodel):
class TranslatedFieldInlineAdminForm(forms.ModelForm):
class Meta:
model = translatemodel
def clean(self):
self.cleaned_data["relation_field"] = relation_field
return self.cleaned_data
class TranslatedTabularInline(generic.GenericTabularInline):
def __init__(self, *args, **kwargs):
super(TranslatedTabularInline, self).__init__(*args, **kwargs)
self.form = translatedFieldInlineAdminFormFactory(self._relation_field_filter, self.model)
extra = 1
max_num = len(LANGUAGES)
exclude = ('relation_field',)
model = None
_relation_field_filter = None
return TranslatedFieldInlineAdminForm
In this way when I need multiple TranslatedCharField I do the following
on the admin:
class TranslatedSlugInline(TranslatedTabularInline):
model = TranslatedCharField
_relation_field_filter = "slug_event_portal"
verbose_name = "Slug"
verbose_name_plural = "Slug"
class TranslatedSlugInline(TranslatedTabularInline):
model = TranslatedCharField
_relation_field_filter = "slug2_event_portal"
verbose_name = "Slug2"
verbose_name_plural = "Slug2"
thanks to the clean method a specific _relation_field_filter is used for each inline
this allow me at least to save them distinguished and retrive them useing the custom queryset
I'm still working on the Model of the Evento to retrive the specific set of values instead the whole related fields but I think I'm not too far now