To improve performance, In my project most of the model instances are stored in cache as list values. But all generic views in Django Rest Framework expect them to be queryset objects. How can I convert the values I got from list into a queryset like objeccts, such that I can use generic views.
Say, I have a function like
def cache_user_articles(user_id):
key = "articles_{0}".format(user_id)
articles = cache.get(key)
if articles is None:
articles = list(Article.objects.filter(user_id = user_id))
cache.set(key, articles)
return articles
In my views.py,
class ArticleViewSet(viewsets.ModelViewSet):
...
def get_queryset(self, request, *args, **kwargs):
return cache_user_articles(kwargs.get(user_id))
But, this of course this wouldn't work as Django Rest Framework expects the result of get_queryset to be QuerySet object, and on PUT request it would call 'get' method on it. Is there any way, I could make it to work with generic DRF views.
That's the place where Python like dynamic languages really shine due to Duck Typing. You could easily write something that quacks like a QuerySet.
import mongoengine
from bson import ObjectId
class DuckTypedQuerySet(list):
def __init__(self, data, document):
if not hasattr(data, '__iter__') or isinstance(data, mongoengine.Document):
raise TypeError("DuckTypedQuerySet requires iterable data")
super(DuckTypedQuerySet, self).__init__(data)
self._document = document
#property
def objects(self):
return self
def _query_match(self, instance, **kwargs):
is_match = True
for key, value in kwargs.items():
attribute = getattr(instance, key, None)
if isinstance(attribute, ObjectId) and not isinstance(value, ObjectId):
attribute = str(attribute)
if not attribute == value:
is_match = False
break
return is_match
def filter(self, **kwargs):
data = filter(lambda instance: self._query_match(instance, **kwargs), self)
return self.__class__(data, self._document)
def get(self, **kwargs):
results = self.filter(**kwargs)
if len(results) > 1:
raise self._document.MultipleObjectsReturned("{0} items returned, instead of 1".format(len(results)))
if len(results) < 1:
raise self._document.DoesNotExist("{0} matching query does not exist.".format(str(self._document)))
return results[0]
def first(self):
return next(iter(self), None)
def all(self):
return self
def count(self):
return len(self)
def cache_user_articles(user_id):
key = "articles_{0}".format(user_id)
articles = cache.get(key)
if articles is None:
articles = DuckTypedQuerySet(list(Article.objects.filter(user_id = user_id)), document = Article)
cache.set(key, articles)
return articles
Ofcourse, this is not an exhaustive implementation. You might need to add other methods that exists in queryset. But I think these will do for simple use-cases. Now you can make do with generic implementations of Django Rest Framework.
What about this?
(I have mocked the redis cache with a class variable)
class CachedManager(models.Manager):
cache = dict()
def cached(self, user_id):
cached = self.cache.get(user_id, [])
if not cached:
self.cache[user_id] = [article.pk for article in self.filter(user_id=user_id)]
return self.cache[user_id]
class Article(models.Model):
objects = CachedManager()
user_id = models.IntegerField()
# Whatever fields your Article model has
Then in your views or wherever you need it:
you can call Article.objects.cached(<a user id>)
Related
I have a serializer for a model with an image field, for which I have saved multiple different sized thumbnail images.
I access them by returning their URL using the SerializerMethodField:
class GalleryImageSerializer(serializers.ModelSerializer):
image_sm = serializers.SerializerMethodField()
image_md = serializers.SerializerMethodField()
image_lg = serializers.SerializerMethodField()
image_compressed = serializers.SerializerMethodField()
def get_image_sm(self, obj):
return default_storage.url(f'{splitext(obj.image.name)[0]}/sm.jpg')
def get_image_md(self, obj):
return default_storage.url(f'{splitext(obj.image.name)[0]}/md.jpg')
def get_image_lg(self, obj):
return default_storage.url(f'{splitext(obj.image.name)[0]}/lg.jpg')
def get_image_compressed(self, obj):
return default_storage.url(f'{splitext(obj.image.name)[0]}/compressed.jpg')
This code works, but it kind of violates the "don't repeat yourself" guideline.
As you can see, these are all duplicate SerializerMethodFields, with the only difference being the filename, eg 'lg.jpg', 'md.jpg', etc.
I'd much prefer to have only one function that I call with an argument for the filename, as an example(pseudocode):
class GalleryImageSerializer(serializers.ModelSerializer):
image_sm = serializers.SerializerMethodField(filename='sm.jpg')
image_md = serializers.SerializerMethodField(filename='md.jpg')
image_lg = serializers.SerializerMethodField(filename='lg.jpg')
image_compressed = serializers.SerializerMethodField(filename='compressed.jpg')
def get_image(self, obj, filename=''):
return default_storage.url(f'{splitext(obj.image.name)[0]}/{filename}')
Currently I am unable to find any way to achieve this. Reading the source code of SerializerMethodField, it doesn't seem to support it.
Is there any way to avoid creating duplicate functions for fields with arbitrary differences?
You can add these fields in the to_representation method.
def to_representation(self, instance):
ret = super().to_representation(instance)
# add img urls to ret dict
for name in ['sm', 'md', 'lg', 'compressed']:
ret['image_' + name] = default_storage.url(f'{splitext(instance.image.name)[0]}/{name}.jpg')
return ret
check the docs for more details:
https://www.django-rest-framework.org/api-guide/serializers/#to_representationself-instance
I want to implement a Django Rest Framework view that returns the dependencies tree of a model instance object. This is the code for such view:
class RoomTypeDependencies(viewsets.ViewSet):
def list(self, request, pk):
room_type = models.RoomType.objects.get(pk=pk)
dependency_tree = self.get_object_dependencies(room_type)
return Response(dependency_tree)
def get_object_dependencies(self, instance):
fields = instance.__class__._meta.get_fields()
dependencies_to_return = []
for field in fields:
print(field.name)
if field.__class__.__name__ == 'ManyToOneRel':
dependency_to_return = []
dependent_instances = getattr(instance, field.name)
for dependent_instance in dependent_instances:
dependency_to_return.append(self.get_object_dependencies(dependent_instance))
dependencies_to_return.append({field.__class__.__name__: dependency_to_return})
return Response({str(instance): dependencies_to_return})
Everything seems to work, but I expected getattr(instance, field.name) to return the dependent instances corresponding to the reverse relationship, just like using model_object_instance.reverse_relationshio_name pattern, but it returns a RelatedManager object instead. The problem in my case is that I have the reverse relationship name in a string variable (field.name).
I have a model with choices list (models.py):
class Product(models.Model):
...
UNITS_L = 1
UNITS_SL = 2
UNITS_XL = 3
PRODUCT_SIZE_CHOICES = (
(UNITS_L, _('L')),
(UNITS_SL, _('SL')),
(UNITS_XL), _('XL'),
)
product_size = models.IntegerField(choices=PRODUCT_SIZE_CHOICES)
...
Also I added a new class for exporting needed fields(admin.py):
from import_export import resources, fields
...
Class ProductReport(resources.ModelResource):
product_size = fields.Field()
class Meta:
model = Product
#I want to do a proper function to render a PRODUCT_SIZE_CHOICES(product_size)
def dehydrate_size_units(self, product):
return '%s' % (product.PRODUCT_SIZE_CHOICES[product_size])
fields = ('product_name', 'product_size')
Class ProductAdmin(ExportMixin, admin.ModelAdmin):
resource_class = ProductReport
But this is not working. How can I get a named value of PRODUCT_SIZE_CHOICES in export by Django import-export ?
You can use 'get_FOO_display' to achieve this in the Django Admin:
class ProductReportResource(resources.ModelResource):
product_size = fields.Field(
attribute='get_product_size_display',
column_name=_(u'Product Size')
)
In my case I was trying to get the display from a foreign key choice field, like:
user__gender
After unsuccessfully trying the accepted answer and the other answer by Waket, I found this thread here:
https://github.com/django-import-export/django-import-export/issues/525
From where I tried a couple of options, and the one that finally worked for me is this:
Create the widget somewhere
from import_export.widgets import Widget
class ChoicesWidget(Widget):
"""
Widget that uses choice display values in place of database values
"""
def __init__(self, choices, *args, **kwargs):
"""
Creates a self.choices dict with a key, display value, and value,
db value, e.g. {'Chocolate': 'CHOC'}
"""
self.choices = dict(choices)
self.revert_choices = dict((v, k) for k, v in self.choices.items())
def clean(self, value, row=None, *args, **kwargs):
"""Returns the db value given the display value"""
return self.revert_choices.get(value, value) if value else None
def render(self, value, obj=None):
"""Returns the display value given the db value"""
return self.choices.get(value, '')
In your model resource declare the field using the widget and passing the choices to it, like this:
user__gender = Field(
widget=ChoicesWidget(settings.GENDER_CHOICES),
attribute='user__gender',
column_name="Gènere",
)
Another solution:
class BaseModelResource(resources.ModelResource):
def export_field(self, field, obj):
field_name = self.get_field_name(field)
func_name = 'get_{}_display'.format(field_name)
if hasattr(obj, func_name):
return getattr(obj, func_name)
return super().export_field(field, obj)
class ProductReportResource(BaseModelResource):
...
I have this models:
from django.db import models
class Item(models.Model):
# ...
def __str__(self):
return 'item'
class SubItemA(Item):
# ...
def __str__(self):
return 'subitem_a'
class SubItemB(Item):
# ...
def __str__(self):
return 'subitem_b'
And I want to call the __str__ method from each Item in Item.object.all() and return each subclass implementation, but it only return the superclass implementation.
Example:
for item in Item.object.all():
print(item.__str__())
On my database I have two SubItemA and two SubItemB. Than it returns:
item
item
item
item
I would like to have some thing like this:
subitem_a
subitem_a
subitem_b
subitem_b
I am lost here.
It's a bit involved. You might look at Django model subclassing: Get the subclass by querying the superclass for a full discussion.
Short answer, from that question:
def related_object(self, default_pointer_name='_ptr'):
models = [A,B] #models
object = None
argument = '%s%s' %(self.__class__.__name__.lower(), default_pointer_name)
query = { argument : self}
for model in models:
try:
object = model.objects.get(**query)
except model.DoesNotExist:
pass
else:
return object
if object == None:
raise RelatedObjectException
return object
# This is a method used by BaseMedium.
With this you will be stumbling upon more problems. A clean solution without reinventing a wheel is to use something like Django Polymorphic
Is there a way to have any #property definitions passed through to a json serializer when serializing a Django model class?
example:
class FooBar(object.Model)
name = models.CharField(...)
#property
def foo(self):
return "My name is %s" %self.name
Want to serialize to:
[{
'name' : 'Test User',
'foo' : 'My name is Test User',
},]
You can extend Django's serializers without /too/ much work. Here's a custom serializer that takes a queryset and a list of attributes (fields or not), and returns JSON.
from StringIO import StringIO
from django.core.serializers.json import Serializer
class MySerializer(Serializer):
def serialize(self, queryset, list_of_attributes, **options):
self.options = options
self.stream = options.get("stream", StringIO())
self.start_serialization()
for obj in queryset:
self.start_object(obj)
for field in list_of_attributes:
self.handle_field(obj, field)
self.end_object(obj)
self.end_serialization()
return self.getvalue()
def handle_field(self, obj, field):
self._current[field] = getattr(obj, field)
Usage:
>>> MySerializer().serialize(MyModel.objects.all(), ["field1", "property2", ...])
Of course, this is probably more work than just writing your own simpler JSON serializer, but maybe not more work than your own XML serializer (you'd have to redefine "handle_field" to match the XML case in addition to changing the base class to do that).
The solution worked well that is proposed by M. Rafay Aleem and Wtower, but it's duplicated lot of code. Here is an improvment:
from django.core.serializers.base import Serializer as BaseSerializer
from django.core.serializers.python import Serializer as PythonSerializer
from django.core.serializers.json import Serializer as JsonSerializer
class ExtBaseSerializer(BaseSerializer):
def serialize_property(self, obj):
model = type(obj)
for field in self.selected_fields:
if hasattr(model, field) and type(getattr(model, field)) == property:
self.handle_prop(obj, field)
def handle_prop(self, obj, field):
self._current[field] = getattr(obj, field)
def end_object(self, obj):
self.serialize_property(obj)
super(ExtBaseSerializer, self).end_object(obj)
class ExtPythonSerializer(ExtBaseSerializer, PythonSerializer):
pass
class ExtJsonSerializer(ExtPythonSerializer, JsonSerializer):
pass
How to use it:
ExtJsonSerializer().serialize(MyModel.objects.all(), fields=['field_name_1', 'property_1' ...])
This is a combination of M. Rafay Aleem and Wtowers answer and caots.
This is DRY and lets you only specify the extra props instead of all fields and props as in caots version.
from django.core.serializers.json import Serializer as JsonSerializer
from django.core.serializers.python import Serializer as PythonSerializer
from django.core.serializers.base import Serializer as BaseSerializer
class ExtBaseSerializer(BaseSerializer):
def serialize(self, queryset, **options):
self.selected_props = options.pop('props')
return super(ExtBaseSerializer, self).serialize(queryset, **options)
def serialize_property(self, obj):
model = type(obj)
for field in self.selected_props:
if hasattr(model, field) and type(getattr(model, field)) == property:
self.handle_prop(obj, field)
def handle_prop(self, obj, field):
self._current[field] = getattr(obj, field)
def end_object(self, obj):
self.serialize_property(obj)
super(ExtBaseSerializer, self).end_object(obj)
class ExtPythonSerializer(ExtBaseSerializer, PythonSerializer):
pass
class ExtJsonSerializer(ExtPythonSerializer, JsonSerializer):
pass
How to use it:
ExtJsonSerializer().serialize(MyModel.objects.all(), props=['property_1', ...])
Things have changed a bit since 2010, so the answer of #user85461 seems to no longer be working with Django 1.8 and Python 3.4. This is an updated answer with what seems to work for me.
from django.core.serializers.base import Serializer as BaseSerializer
from django.core.serializers.python import Serializer as PythonSerializer
from django.core.serializers.json import Serializer as JsonSerializer
from django.utils import six
class ExtBaseSerializer(BaseSerializer):
""" Abstract serializer class; everything is the same as Django's base except from the marked lines """
def serialize(self, queryset, **options):
self.options = options
self.stream = options.pop('stream', six.StringIO())
self.selected_fields = options.pop('fields', None)
self.selected_props = options.pop('props', None) # added this
self.use_natural_keys = options.pop('use_natural_keys', False)
self.use_natural_foreign_keys = options.pop('use_natural_foreign_keys', False)
self.use_natural_primary_keys = options.pop('use_natural_primary_keys', False)
self.start_serialization()
self.first = True
for obj in queryset:
self.start_object(obj)
concrete_model = obj._meta.concrete_model
for field in concrete_model._meta.local_fields:
if field.serialize:
if field.rel is None:
if self.selected_fields is None or field.attname in self.selected_fields:
self.handle_field(obj, field)
else:
if self.selected_fields is None or field.attname[:-3] in self.selected_fields:
self.handle_fk_field(obj, field)
for field in concrete_model._meta.many_to_many:
if field.serialize:
if self.selected_fields is None or field.attname in self.selected_fields:
self.handle_m2m_field(obj, field)
# added this loop
if self.selected_props:
for field in self.selected_props:
self.handle_prop(obj, field)
self.end_object(obj)
if self.first:
self.first = False
self.end_serialization()
return self.getvalue()
# added this function
def handle_prop(self, obj, field):
self._current[field] = getattr(obj, field)
class ExtPythonSerializer(ExtBaseSerializer, PythonSerializer):
pass
class ExtJsonSerializer(ExtPythonSerializer, JsonSerializer):
pass
Usage:
>>> ExtJsonSerializer().serialize(MyModel.objects.all(), fields=['myfield', ...], props=['myprop', ...])
You can get all of the properties of a class using some black magic:
def list_class_properties(cls):
return [k for k,v in cls.__dict__.iteritems() if type(v) is property]
For example:
>>> class Foo:
#property
def bar(self):
return "bar"
>>> list_class_properties(Foo)
['bar']
Then you can build the dictionary and serialize it from there.