Let's say I have model like this:
class Article(models.Model):
...
def get_route(self):
...
return data
When I want to display value returned by get_route() in admin panel I have to create custom method like this:
def list_display_get_route(self, obj):
return obj.get_route()
list_display = ('list_display_get_route', ...)
I've recently discovered that if I decorate get_route() with #property there's no need to use list_display_get_route() and the following will work:
models.py
#property
def get_route(self):
...
return data
admin.py
list_display = ('get_route', ...)
Is this correct approach? Are there any drawbacks of this solution?
Using get_route in list_display should work even if you don't make it a property.
class Article(models.Model):
def get_route(self):
...
return data
class ArticleAdmin(admin.ModelAdmin):
list_display = ('get_route', ...)
See the list_display docs for the list of value types supported. The fourth example, "a string representing an attribute on the model", is equivalent to your get_route method.
Using the property decorator is fine if you prefer, but in this case it would make more sense to name it route.
#property
def route(self):
...
return data
class ArticleAdmin(admin.ModelAdmin):
list_display = ('route', ...)
Related
I cant create some logic(for me it interesting). For example i have View like this:
class DucktList(generics.ListAPIView):
serializer_class = DuckSerializer
filter_backends = (DjangoFilterBackend,)
filter_fields = ('test_field',) // i want to create some custom field and filter by it if needed.
serializer:
class DuckSerializer(serializers.ModelSerializer):
test_field = SerializerMethodField() // i want filter by this field!
def get_test_field(self, obj):
return True
class Meta:
......
How i can filter filter_fields with test_field ?
Perhaps you could define your own filter with custom methods and using this library django-filters:
from django_filters import rest_framework as filters
class EventFilter(filters.FilterSet):
finish_on = filters.BooleanFilter(name='finish_on', method='filter_manifestation')
begin_on = filters.BooleanFilter(name='begin_on', method='filter_manifestation')
def filter_manifestation(self, queryset, name, value):
if value is False:
lookup = '__'.join([name, 'gte'])
else:
lookup = '__'.join([name, 'lte'])
qs = queryset.filter(**{lookup: timezone.now()})
return qs
class Meta:
model = Event
fields = [
'finished', 'has_begun'
]
And add this filter to your view:
class ManifestationViewSet(viewsets.ReadOnlyModelViewSet):
...
filter_class = EventFilter
...
You could then adapt your filter's customs methods depending on what you want to do in the related function of your serializer custom field.
You have some snippets on the django-filter library doc about various filters types.
I have a model with a self referential field called parent.
Model:
class Zone(BaseModel):
name = models.CharField(max_length=200)
parent = models.ForeignKey('self', models.CASCADE, blank=True, null=True, related_name='children')
def __unicode__(self):
return self.name
Serializer:
class ZoneSerializer(ModelSerializer):
parent = PrimaryKeyRelatedField(many=False, queryset=Zone.objects.all())
parent_disp = StringRelatedField(many=False, source="parent")
class Meta:
model = Zone
fields = ('id', 'name', 'parent', 'parent_disp')
Now I want to serialize the parent of the zone and its parent and its parent till parent is none.
I found recursive serialization methods for children but not for parent.
How can I do this?
Ok, I got it working like that.
class ZoneSerializer(ModelSerializer):
parent = SerializerMethodField()
class Meta:
model = Zone
fields = ('id', 'name', 'project', 'parent',)
def get_parent(self, obj):
if obj.parent is not None:
return ZoneSerializer(obj.parent).data
else:
return None
Try use SerializerMethodField here:
def get_parent(self, obj):
# query what your want here.
I'm not sure D-R-F has build-in methods for this, but you can use query to get what you want in this method.
You also can do:
class ZoneSerializer(ModelSerializer):
class Meta:
model = Zone
fields = ('id', 'name', 'project', 'parent',)
def to_representation(self, instance):
self.fields['parent'] = ZoneSerializer(read_only=True)
return super(ZoneSerializer, self).to_representation(instance)
Just wanted to add an additional solution that worked better for me.
The answer above using the SerializerMethodField and then instantiating a new serializer class works nicely, but if you are trying to serialize a larger nested dataset instantiating a new serializer for each of your nested objects might become quite slow.
Trying to use a single instance of the serializer for all the nested objects made it a lot faster.
class ZoneSerializer(ModelSerializer):
...
def get_parent(self, obj):
if obj.parent is not None:
return self.to_representation(obj)
else:
return None
One more step could be to also convert your return of the serializer method field to a ReturnDict instance, which is what a serializer normally does when you call serializer.data property.
from rest_framework.utils.serializer_helpers import ReturnDict
class ZoneSerializer(ModelSerializer):
...
def get_parent(self, obj):
if obj.parent is not None:
return ReturnDict(self.to_representation(obj), serializer=self)
else:
return None
There are some potential disadvantages of this approach of course, like loosing validation that happens when instantiating a serializer, harder to modify for nested objects, etc.
If you are confident in your serializer setup and data being passed to the serializer this can be a nice optimization.
I want to create a ListView with a array of nested objects. Here what I've tried so far:
rest.py
class GroupDetailSerializer(serializers.ModelSerializer):
class Meta:
model = Group
fields = (
'id',
'num',
'students',
)
#permission_classes((permissions.IsAdminUser,))
class GroupDetailView(mixins.ListModelMixin, viewsets.GenericViewSet):
serializer_class = GroupDetailSerializer
def get_queryset(self):
return Group.objects.all()
models.py
class Group(models.Model):
office = models.ForeignKey(Offices)
num = models.IntegerField()
#property
def students(self):
from pupils.models import Pupils
return Pupils.objects.filter(group=self)
But it returns a type error:
<Pupils: John Doe> is not JSON serializable
I guess I need to use another serializer on my students field, but how?
Error is because your model is not json serializable.
you can see #yuwang comment to follow nested serializer http://www.django-rest-framework.org/api-guide/serializers/#dealing-with-nested-objects
or for now, particular for this case you can change your code to:
#property
def students(self):
from pupils.models import Pupils
return list(Pupils.objects.filter(group=self).values())
I would like to filter against query params in my REST API - see django docs on this.
However, one parameter I wish to filter by is only available via a model #property
example models.py:
class Listing(models.Model):
product = models.OneToOneField(Product, related_name='listing')
...
#property
def category(self):
return self.product.assets[0].category.name
Here is the setup for my Listing API in accordance with django-filter docs
class ListingFilter(django_filters.FilterSet):
product = django_filters.CharFilter(name='product__name')
category = django_filters.CharFilter(name='category') #DOES NOT WORK!!
class Meta:
model = Listing
fields = ['product','category']
class ListingList(generics.ListCreateAPIView):
queryset = Listing.objects.all()
serializer_class = ListingSerializer
filter_class = ListingFilter
How can I appropriately filter by listing.category? It is not available on the listing model directly.
Use the 'action' parameter to specify a custom method - see django-filter docs
First define a method that filters a queryset using the value of the category parameter:
def filter_category(queryset, value):
if not value:
return queryset
queryset = ...custom filtering on queryset using 'value'...
return queryset
Listing filter should look like this:
class ListingFilter(django_filters.FilterSet):
...
category = django_filters.CharFilter(action=filter_category)
...
For sake of database speed, you should just add the category to your listing model
class Listing(models.Model):
product = models.OneToOneField(Product, related_name='listing')
category = models.ForeignKey(Category)
Then use a post_save signal to keep the field updated
from django.dispatch import receiver
from django.db.models.signals import post_save
#receiver(post_save, sender=Product)
def updateCategory(sender, instance, created, update_fields, **kwargs):
product = instance
product.listing.category = product.assets[0].category.name
product.listing.save()
Then filter by it's name as you would any other field:
class ListingFilter(django_filters.FilterSet):
...
category = django_filters.CharFilter(name='category__name')
...
The Django admin docs says that it is possible to specify a callable as a value that can be used in list_display. If I need to pass some extra context to the function via function arguments, what's the best way to accomplish that?
In pseudo code, what I'd like to do is something like:
App realestate:
models.py:
class A(models.Model):
raw = models.TextField()
admin.py:
from utils import processing
list_display = [processing('realestate app result', True)]
App party:
models.py:
class Person(models.Model):
raw = models.TextField()
admin.py:
from utils import processing
list_display = [processing('party app result', False)]
utils.py:
def processing(obj, short_description, allow_tags=False):
def process(obj):
# do something
pass
process.short_description = short_description
process.allow_tags = allow_tags
return process(obj)
You should not specify the short_description and allow_tags inside the function itself.
define processing like this:
utils.py:
def processing(obj):
#do something
pass
on each ModelAdmin class, do:
class RealestateAdmin(...):
list_display = [processing,]
def processing(obj):
return utils.processing(obj)
processing.short_description = 'realestate app result'
processing.allow_tags = True
class PartyAdmin(...):
list_display = [processing,]
def processing(obj):
return utils.processing(obj)
processing.short_description = 'party app result'
processing.allow_tags = False
So you have one place (utils.py) which contains the logic.
And every class defines the properties to display in the table.