How to reuse a serializer in Django (Python) - python

I have a service in a Django app that I am building where I want to handle get and post requests. I though I should reuse a serializer I've built but in the examples I found, whenever someone wants to use a serializer they create a new object.
This is an implementation where the serializer class is called multiple times to create multiple instances, one each time a request arrives:
from django.http.response import JsonResponse
from django.http.request import RAISE_ERROR, HttpRequest
from rest_framework.parsers import JSONParser
from rest_framework import status
from models import Instrument
from serializers import InstrumentsSerializer
class InstrumentsService():
def __init__(self):
self.serializer: InstrumentsSerializer = None
def get_instruments_by_type(self, instrument_type: str):
if instrument_type is not None:
instruments = Instrument.objects.all()
instruments.filter(instrument_type__icontains=instrument_type)
instruments_serializer = InstrumentsSerializer(instruments, many=True)
else:
raise ValueError("Value type None is not acceptable for 'instrument_type'")
return instruments_serializer.data
def add_instrument(self, instrument_data: Instrument):
instrument_serializer = InstrumentsSerializer(data=instrument_data)
if instrument_serializer.is_valid():
instrument_serializer.save()
How can I use the same serializer and pass different data to it each time? Because in the example I presented, the data are being passed during initialization.

Why not use Python Class Objects?
class InstrumentsService():
serializer = InstrumentsSerializer
def __init__(self):
pass
def get_instruments_by_type(self, instrument_type: str):
if instrument_type is not None:
instruments = Instrument.objects.all()
instruments.filter(instrument_type__icontains=instrument_type)
instruments_serializer = InstrumentsService.serializer(instruments, many=True)

Related

Why is django not using my custom encoder class?

I have two classes: Website and WordpressWebsite.
WordpressWebsite subclasses Website.
When an instance of WordpressWebsite is being encoded into JSON, only the attributes of WordpressWebsite are present (and none of the attributes of Website).
My goal is to write a custom encoder which will encode a WordpressWebsite as a Website instead.
This is what I have so far:
from django.core.serializers.json import DjangoJSONEncoder
from websites.models import Website
class WebsiteEncoder(DjangoJSONEncoder):
def default(self, obj):
raise Exception() # TEST
if isinstance(obj, Website) and hasattr(obj, 'website_ptr'):
return super().default(obj.website_ptr)
return super().default(obj)
I have the following test case:
from django.core import serializers
from django.test import TestCase
from websites.models.wordpress import WordpressWebsite
from websites.serialize import WebsiteEncoder
class SerializationTest(TestCase):
def setUp(self):
self.wordpress = WordpressWebsite.objects.create(
domain='test.com'
)
def test_foo(self):
JSONSerializer = serializers.get_serializer("json")
json_serializer = JSONSerializer()
json_serializer.serialize(
WordpressWebsite.objects.all(),
cls=WebsiteEncoder
)
data = json_serializer.getvalue()
print(data)
This test case runs fine. It does not raise an exception.
Does anyone know why WebsiteEncoder.default is not being invoked?
Django models are encoded natively with its serializers. Django's own DjangoJSONEncoder supplies a complete serializer for all possible models with any of the default Django datatypes. If you look at the JSONEncoder.default() documentation, you'll notice that you would only supply encoders for datatypes that are not yet known to the encoder.
Only if you were using a field type which Django doesn't natively support, you could provide an encoder for it - and only that field type - through .default(). Therefore DjangoJSONEncoder isn't what you're looking for.
Trying to make your example work I discovered you can actually customize the process by subclassing django.core.serializers.json.Serializer:
from django.core.serializers.json import Serializer
class WebsiteSerializer(Serializer):
def get_dump_object(self, obj):
return {
"pk": obj.pk,
**self._current,
}
After that, you can make your serializer work in the test case like so:
def test_foo(self):
serializer = WebsiteSerializer()
data = serializer.serialize(WordpressWebsite.objects.all())
print(data)

How to put sessions on all pages without putting the session code on all views in pyramid?

I think my problem is related on the way I structured my pyramid project.
What I want to accomplish is to make my code runs on all views, I don't want to paste same codes on all views. Its like I will include the code in all views by just simply calling it. This is my code.
my wizard module
from pyramid.view import view_config, view_defaults
from .models import *
from datetime import datetime
from pyramid.response import Response
from bson import ObjectId
from pyramid.httpexceptions import HTTPFound
import json
class WizardView:
def __init__(self, request):
self.request = request
#view_config(route_name='wizard', renderer='templates/wizard.jinja2')
def wizard(self):
session = self.request.session
if session:
return {'fullname':session['name'],'userrole':session['userrole']}
else:
url = self.request.route_url('login')
return HTTPFound(location=url)
my bill module
from pyramid.view import view_config, view_defaults
from .models import *
from datetime import datetime
from pyramid.response import Response
from bson import ObjectId
from pyramid.httpexceptions import HTTPFound
class BillView:
def __init__(self, request):
self.request = request
#view_config(route_name='bills', renderer='templates/bills.jinja2')
def bills(self):
session = self.request.session
if session:
return {'fullname':session['name'],'userrole':session['userrole']}
else:
url = self.request.route_url('login')
return HTTPFound(location=url)
As you can see I have to paste this code twice (this code checks if session exist, if not, then redirect user to login page)
session = self.request.session
if session:
return {'fullname':session['name'],'userrole':session['userrole']}
else:
url = self.request.route_url('login')
return HTTPFound(location=url)
I've tried to search and I think what I need is some sort of auto loader? How can I apply this on pyramid? Or should I stick with this process?
If you wish to return exactly the same thing from both (many) views, then the best way is to use inheritance.
class GenericView:
def __init__(self, request):
self.request = request
def generic_response(self):
session = self.request.session
if session:
return {'fullname':session['name'],'userrole':session['userrole']}
else:
url = self.request.route_url('login')
return HTTPFound(location=url)
Use generic_response in WizardView and BillView
class WizardView(GenericView):
def __init__(self, request):
super().__init__(request)
# Do wizard specific initialization
#view_config(route_name='wizard', renderer='templates/wizard.jinja2')
def wizard(self):
return self.generic_response()
class BillView(GenericView):
def __init__(self, request):
super().__init__(request)
# Do bill specific initialization
#view_config(route_name='bills', renderer='templates/bills.jinja2')
def bills(self):
return self.generic_response()
If you want to just check (and redirect) when session does not exists and otherwise proceed normally you could use custom exceptions and corresponding views.
First define custom exception
class SessionNotPresent(Exception):
pass
And view for this exception
#view_config(context=SessionNotPresent)
def handle_no_session(context, request):
# context is our custom exception, request is normal request
request.route_url('login')
return HTTPFound(location=url)
Then just check if session exists in parent constructor
class SessionView:
def __init__(self, request):
self.request = request
if not self.request.session:
raise SessionNotPresent() # Pyramid will delegate request handilng to handle_no_request
In views simply extend SessionView and non existing sessions will be handeled by handle_no_session.
class WizardView(SessionView):
def __init__(self, request):
super().__init__(request)
# Do wizard specific initialization
#view_config(route_name='wizard', renderer='templates/wizard.jinja2')
def wizard(self):
session = self.request.session
return {'fullname':session['name'],'userrole':session['userrole']}
class BillView(SessionView):
def __init__(self, request):
super().__init__(request)
# Do bill specific initialization
#view_config(route_name='bills', renderer='templates/bills.jinja2')
def bills(self):
session = self.request.session
return {'fullname':session['name'],'userrole':session['userrole']}
You could easily add additional parameters to exception (redirect_url, ...)
For exception handling see http://docs.pylonsproject.org/projects/pyramid-cookbook/en/latest/pylons/exceptions.html
You can use events: http://docs.pylonsproject.org/projects/pyramid/en/latest/narr/events.html
Example:
from pyramid.events import subscriber, NewRequest
#subscriber(NewRequest)
def check_session(event):
if not event.request.session:
raise HTTPFound(location=event.request.route_path('login'))

Where does the self.get_serializer method come from in the Django REST Framework?

In the DRF source code, there's a get_serializer method. It wasn't inherited from object and it's not a method in the CreateModelMixin class. Where does this method come from?
serializer = self.get_serializer(data=request.data)
Here's the larger chunk of code for context.
from __future__ import unicode_literals
from rest_framework import status
from rest_framework.response import Response
from rest_framework.settings import api_settings
class CreateModelMixin(object):
"""
Create a model instance.
"""
def create(self, request, *args, **kwargs):
serializer = self.get_serializer(data=request.data)
serializer.is_valid(raise_exception=True)
self.perform_create(serializer)
headers = self.get_success_headers(serializer.data)
return Response(serializer.data, status=status.HTTP_201_CREATED, headers=headers)
def perform_create(self, serializer):
serializer.save()
def get_success_headers(self, data):
try:
return {'Location': data[api_settings.URL_FIELD_NAME]}
except (TypeError, KeyError):
return {}
There are a few SO posts that that also use this method. Like this, this, and this. But I still can't figure out where the implementation is.
CreateModelMixin along with all other mixin classes (Eg. ListModelMixin, UpdateModelMixin etc) are defined in rest_framework/mixins.py file.
These mixin classes provide all the basic CRUD operations on a model. You just need to define a serializer_class and queryset in your generic view to perform all these operations. DRF has separated out these common functionality in separate mixin classes so that they can be injected/mixed-in in a view and used as and when required.
In the DRF source code, there's a get_serializer method. It wasn't
inherited from object and it's not a method in the CreateModelMixin
class. Where does this method come from?
In the GenericAPIView, get_serializer method is defined. The combination of different mixin classes along with GenericAPIView class provide us different generic views for different use cases.
class GenericAPIView(views.APIView):
"""
Base class for all other generic views.
"""
def get_serializer(self, *args, **kwargs):
"""
Return the serializer instance that should be used for validating and
deserializing input, and for serializing output.
"""
serializer_class = self.get_serializer_class()
kwargs['context'] = self.get_serializer_context()
return serializer_class(*args, **kwargs)
Other generic views then inherit the relevant mixin along with GenericAPIView.
Eg. CreateAPIView inherit the CreateModelMixin along with GenericAPIView to provide create-only endpoints.
# rest_framework/generics.py
class CreateAPIView(mixins.CreateModelMixin,
GenericAPIView):
...
It helps if you understand class inheritance (not suggesting you don't, though).
CreateModelMixin will be used by a class based view, for example:
class YourViewSet(mixins.CreateModelMixin, viewsets.GenericViewSet):
get_serializer is a method available through GenericViewSet (although there are probably other View classes that it's available on, too).
So, basically if you would use CreateModelMixin only get_serializer would not be available. (You probably wouldn't do that anyway). It's only because you inherit from another class besides CreateModelMixin that the get_serializer method is available at the moment that the create method is executed.
I could give a simple example of what's happening, while I am at it. It's just to give a more simple overview of what's happening.
disclaimer: you might want to keep in mind that I am a junior developer, so this may not be the most Pythonic code out there :P.
class MyMixin:
def create(self):
serializer = self.get_serializer()
return serializer
class FakeView:
def get_serializer(self):
return "Sadly I am just a string, but I could've been a serializer."
class MyFakeView(MyMixin, FakeView):
pass
view = MyFakeView()
serializer = view.create()
print(serializer)
executing this would show you:
Sadly I am just a string, but I could've been a serializer.
Where if you would define MyFakeView like below,
class MyFakeView(MyMixin):
pass
you would receive the following error:
AttributeError: 'MyFakeView' object has no attribute 'get_serializer'
You can see the __file__ or __module__ member of the method (if it has them) to learn that. inspect also has getsourcefile and getsourcelines that use data from the function's code object, specifically, <function>.f_code.co_filename and .co_firstline.
For example, this retrieves source information for a method inherited from DictMixin:
>>> c=ConfigParser._Chainmap()
>>> f=c.__len__
>>> dump(f.__code__) # my custom function that dumps attributes,
# see https://github.com/native-api/dump
<...>
co_filename : /usr/lib/python2.7/UserDict.py
co_firstlineno : 179
<...>
# same with inspect
>>> inspect.getsourcefile(f)
'/usr/lib/python2.7/UserDict.py'
>>> inspect.getsourcelines(f)
([' def __len__(self):\n', ' return len(self.keys())\n'], 179)
>>> inspect.getsource(f)
' def __len__(self):\n return len(self.keys())\n'
# same with __file__
>>> sys.modules[f.__module__].__file__
'/usr/lib/python2.7/UserDict.pyc'

Django Class Based View use #cache_page [duplicate]

I'm trying to do cache_page with class based views (TemplateView) and i'm not able to. I followed instructions here:
Django--URL Caching Failing for Class Based Views
as well as here:
https://github.com/msgre/hazard/blob/master/hazard/urls.py
But I get this error:
cache_page has a single mandatory positional argument: timeout
I read the code for cache_page and it has the following:
if len(args) != 1 or callable(args[0]):
raise TypeError("cache_page has a single mandatory positional argument: timeout")
cache_timeout = args[0]
which means it wont allow more than 1 argument. Is there any other way to get cache_page to work?? I have been digging into this for sometime...
It seems like the previous solutions wont work any longer
According to the caching docs, the correct way to cache a CBV in the URLs is:
from django.views.decorators.cache import cache_page
url(r'^my_url/?$', cache_page(60*60)(MyView.as_view())),
Note that the answer you linked to is out of date. The old way of using the decorator has been removed (changeset).
You can simply decorate the class itself instead of overriding the dispatch method or using a mixin.
For example
from django.views.decorators.cache import cache_page
from django.utils.decorators import method_decorator
#method_decorator(cache_page(60 * 5), name='dispatch')
class ListView(ListView):
...
Django docs on decorating a method within a class based view.
yet another good example CacheMixin
from cyberdelia github
class CacheMixin(object):
cache_timeout = 60
def get_cache_timeout(self):
return self.cache_timeout
def dispatch(self, *args, **kwargs):
return cache_page(self.get_cache_timeout())(super(CacheMixin, self).dispatch)(*args, **kwargs)
usecase:
from django.views.generic.detail import DetailView
class ArticleView(CacheMixin, DetailView):
cache_timeout = 90
template_name = "article_detail.html"
queryset = Article.objects.articles()
context_object_name = "article"
You can add it as a class decorator and even add multiple using a list:
#method_decorator([vary_on_cookie, cache_page(900)], name='dispatch')
class SomeClass(View):
...
I created this little mixin generator to do the caching in the views file, instead of in the URL conf:
def CachedView(cache_time=60 * 60):
"""
Mixing generator for caching class-based views.
Example usage:
class MyView(CachedView(60), TemplateView):
....
:param cache_time: time to cache the page, in seconds
:return: a mixin for caching a view for a particular number of seconds
"""
class CacheMixin(object):
#classmethod
def as_view(cls, **initkwargs):
return cache_page(cache_time)(
super(CacheMixin, cls).as_view(**initkwargs)
)
return CacheMixin
Yet another answer, we found this to be simplest and is specific to template views.
class CachedTemplateView(TemplateView):
#classonlymethod
def as_view(cls, **initkwargs): ##NoSelf
return cache_page(15 * 60)(super(CachedTemplateView, cls).as_view(**initkwargs))
Would like to add this:
If you need to use multiple decorators for cache like vary_on_headers and cache_page together, here is one way I did:
class CacheHeaderMixin(object):
cache_timeout = int(config('CACHE_TIMEOUT', default=300))
# cache_timeout = 60 * 5
def get_cache_timeout(self):
return self.cache_timeout
def dispatch(self, *args, **kwargs):
return vary_on_headers('Authorization')(cache_page(self.get_cache_timeout())(super(CacheHeaderMixin, self).dispatch))(*args, **kwargs)
This way cache is stored and it varies for different Authorization header (JWT). You may use like this for a class based view.
class UserListAPIView(CacheHeaderMixin, ListAPIView):
serializer_class = UserSerializer
def get_queryset(self):
return CustomUser.objects.all()
I didn't found a good cache solution for class based views and created my own: https://gist.github.com/svetlyak40wt/11126018
It is a mixin for a class. Add it before the main base class and implement method get_cache_params like that:
def get_cache_params(self, *args, **kwargs):
return ('some-prefix-{username}'.format(
username=self.request.user.username),
3600)
Here's my variation of the CachedView() mixin - I don't want to cache the view if the user is authenticated, because their view of pages will be unique to them (e.g. include their username, log-out link, etc).
class CacheMixin(object):
"""
Add this mixin to a view to cache it.
Disables caching for logged-in users.
"""
cache_timeout = 60 * 5 # seconds
def get_cache_timeout(self):
return self.cache_timeout
def dispatch(self, *args, **kwargs):
if hasattr(self.request, 'user') and self.request.user.is_authenticated:
# Logged-in, return the page without caching.
return super().dispatch(*args, **kwargs)
else:
# Unauthenticated user; use caching.
return cache_page(self.get_cache_timeout())(super().dispatch)(*args, **kwargs)

Django Rest Framework add class level method to api

I am using Django Rest Framework to create my API. I am using #link to return information about a particular object.
class SomeClassView(viewsets.ReadOnlyModelViewSet):
#link
def info(self, request, pk=None):
obj = models.SomeClass.objects.get(pk=pk)
return Response({"info": object.info()})
GET: /someclass/1/info
What I would like to do is extend the method so I can access it at the "class level" so that my api could accept a list of objects
class SomeClassView(viewsets.ReadOnlyModelViewSet):
#link
def info(self, request, pk=None):
if isinstance(s, str):
obj = models.SomeClass.objects.get(pk=pk)
return Response({"info": obj.info()})
else:
objs = models.SomeClass.objects.filter(pk__in=pk).all()
return Response({"infos": [obj.info() for obj in objs]})
GET: /someclass/1/info
GET: /someclass/info?pk=1&pk=2&pk=3
Is there a way I can add a class level method to my api? Or will I need to create a new class to handle the one api call?
PS: I don't mind if I need to have a separate method to make this work
The dynamically generated routes using the #link or #action decorators are hard-coded to look like /someclass/<pk>/<methodname>/. You can expose a /someclass/info endpoint by adding a custom route, e.g.:
class MyRouter(DefaultRouter):
routes = [
Route(
url=r'^{prefix}/((?P<pk>\d+)/)?info$',
mapping={'get': 'info'},
name='{basename}-info',
initkwargs={}
)
] + DefaultRouter.routes
Then your info method might look like this:
def info(self, request, pk=None):
if pk:
obj = SomeClass.objects.get(pk=pk)
return Response({'info': obj.info()})
else:
objs = SomeClass.objects.filter(pk__in=request.GET.getlist('pk'))
return Response({'infos': [obj.info() for obj in objs]})
(Note the absence of the #link decorator.)

Categories

Resources