Python

  • Django OAuth Toolkit: Allow access token expiration date per user

    In this tutorial, I will demonstrate how to implement a per-user access token expiration for Django OAuth Toolkit. Setup OAuth Toolkit Override OAUTH2_VALIDATOR_CLASS in settings.py OAUTH2_PROVIDER = { 'ACCESS_TOKEN_EXPIRE_SECONDS': 1800, # 30 minutes 'REFRESH_TOKEN_EXPIRE_SECONDS': 3600, # 1 hour 'OAUTH2_VALIDATOR_CLASS': 'py_app.validator.MyOAuth2Validator', }   Custom Validator Create the custom validator Create MyOAuth2Validator.py inside <app-root>/py_app/validator *Create validator folder if it doesn't exist yet   Override save_bearer_token method to check for our custom expiration field and use it if there's any. from oauth2_provider.oauth2_validators import OAuth2Validator from oauth2_provider.models import AccessToken class MyOAuth2Validator(OAuth2Validator): """ Primarily extend the functionality of token generation """ def save_bearer_token(self, token, request, *args, **kwargs): from datetime import datetime, timedelta super(MyOAuth2Validator, self).save_bearer_token(token, request, *args, **kwargs) ip = self.get_client_ip(request) accessToken = AccessToken.objects.get(token=token.get('access_token')) if accessToken.user.detail.session_expire_in is not None: accessToken.expires = datetime.now() + timedelta(seconds=accessToken.user.detail.session_expire_in) accessToken.save()   Custom field We first need to create a model that we can link to our user model. We can call it UserDetail. Create this class on a dedicated django app or on any existing app model. class UserDetail(models.Model): user = models.OneToOneField(USER_MODEL, related_name='detail', on_delete=models.CASCADE, null=True, blank=True) session_expire_in = models.IntegerField(default=None, null=True, blank=True)   And that's it.. Run the makemigrations, migrate and runserver.   Have fun!
  • drf

    Fix Django REST framework AttributeError: 'Request' object has no attribute 'accepted_renderer'

    Below are ways to fix AttributeError: 'Request' object has no attribute 'accepted_renderer' error.   1. Install pyyaml pip install pyyaml   OR   2. Revert Django REST Framework (DRF) pip install djangorestframework==3.8.0   OR   3. Ignore decode on your ViewSet class MyCoolViewset(viewsets.ModelViewSet): def _clean_data(self, data): if isinstance(data, bytes): data = data.decode(errors='ignore') return super(MyCoolViewset, self)._clean_data(data)  
  • graphql order by

    Create orderBy filter on Django Graphene

    We can add orderBy filter on DjangoFilterConnectionField and create a resolver to get the arguments and pass to our queryset.  Sample code: import graphene import graphql_jwt import django_filters from graphene import relay, ObjectType from graphene_django.types import DjangoObjectType from graphene_django.filter import DjangoFilterConnectionField class BrandType(DjangoObjectType): class Meta: model = Brand filter_fields = { 'name': ['exact', 'icontains', 'istartswith'], 'code': ['exact', 'icontains', 'istartswith'], 'id': ['exact', 'in'], } interfaces = (relay.Node, ) class Query(graphene.ObjectType): brand = relay.Node.Field(BrandType) all_brand = DjangoFilterConnectionField(BrandType, orderBy=graphene.List(of_type=graphene.String)) def resolve_all_brand(self, info, **kwargs): orderBy = kwargs.get('orderBy', None) return Brand.objects.order_by(*orderBy) Sample query:  query{ allBrand(orderBy:["weight"]) { edges{ node{ name, weight, code } } } } Sample result: { "data": { "allBrand": { "edges": [ { "node": { "name": "Not Applicable", "weight": -99, "code": "N/A" } }, { "node": { "name": "AFP", "weight": 0, "code": null } }, { "node": { "name": "AGFA", "weight": 0, "code": null } } } ] } } }  
  • Error pip install mysqlclient on MacOSX

     If you have the similar issue as below ERROR: Command errored out with exit status 1: command: /.../bin/python -u -c 'import sys, setuptools, tokenize; sys.argv[0] = '"'"'/private/var/folders/53/l74q51892zjgsql64hkvhw180000gn/T/pip-install-e_x8dx0d/mysqlclient/setup.py'"'"'; __file__='"'"'/private/var/folders/53/l74q51892zjgsql64hkvhw180000gn/T/pip-install-e_x8dx0d/mysqlclient/setup.py'"'"';f=getattr(tokenize, '"'"'open'"'"', open)(__file__);code=f.read().replace('"'"'\r\n'"'"', '"'"'\n'"'"');f.close();exec(compile(code, __file__, '"'"'exec'"'"'))' bdist_wheel -d /private/var/folders/53/l74q51892zjgsql64hkvhw180000gn/T/pip-wheel-8pw_7779 cwd: /private/var/folders/53/l74q51892zjgsql64hkvhw180000gn/T/pip-install-e_x8dx0d/mysqlclient/ Complete output (30 lines): running bdist_wheel running build running build_py creating build creating build/lib.macosx-10.9-x86_64-3.7 creating build/lib.macosx-10.9-x86_64-3.7/MySQLdb copying MySQLdb/__init__.py -> build/lib.macosx-10.9-x86_64-3.7/MySQLdb copying MySQLdb/_exceptions.py -> build/lib.macosx-10.9-x86_64-3.7/MySQLdb copying MySQLdb/compat.py -> build/lib.macosx-10.9-x86_64-3.7/MySQLdb copying MySQLdb/connections.py -> build/lib.macosx-10.9-x86_64-3.7/MySQLdb copying MySQLdb/converters.py -> build/lib.macosx-10.9-x86_64-3.7/MySQLdb copying MySQLdb/cursors.py -> build/lib.macosx-10.9-x86_64-3.7/MySQLdb copying MySQLdb/release.py -> build/lib.macosx-10.9-x86_64-3.7/MySQLdb copying MySQLdb/times.py -> build/lib.macosx-10.9-x86_64-3.7/MySQLdb creating build/lib.macosx-10.9-x86_64-3.7/MySQLdb/constants copying MySQLdb/constants/__init__.py -> build/lib.macosx-10.9-x86_64-3.7/MySQLdb/constants copying MySQLdb/constants/CLIENT.py -> build/lib.macosx-10.9-x86_64-3.7/MySQLdb/constants copying MySQLdb/constants/CR.py -> build/lib.macosx-10.9-x86_64-3.7/MySQLdb/constants copying MySQLdb/constants/ER.py -> build/lib.macosx-10.9-x86_64-3.7/MySQLdb/constants copying MySQLdb/constants/FIELD_TYPE.py -> build/lib.macosx-10.9-x86_64-3.7/MySQLdb/constants copying MySQLdb/constants/FLAG.py -> build/lib.macosx-10.9-x86_64-3.7/MySQLdb/constants running build_ext building 'MySQLdb._mysql' extension creating build/temp.macosx-10.9-x86_64-3.7 creating build/temp.macosx-10.9-x86_64-3.7/MySQLdb gcc -Wno-unused-result -Wsign-compare -Wunreachable-code -fno-common -dynamic -DNDEBUG -g -fwrapv -O3 -Wall -arch x86_64 -g -Dversion_info=(1,4,2,'post',1) -D__version__=1.4.2.post1 -I/usr/local/Cellar/mysql/8.0.19/include/mysql -I/Library/Frameworks/Python.framework/Versions/3.7/include/python3.7m -c MySQLdb/_mysql.c -o build/temp.macosx-10.9-x86_64-3.7/MySQLdb/_mysql.o gcc -bundle -undefined dynamic_lookup -arch x86_64 -g build/temp.macosx-10.9-x86_64-3.7/MySQLdb/_mysql.o -L/usr/local/Cellar/mysql/8.0.19/lib -lmysqlclient -lssl -lcrypto -o build/lib.macosx-10.9-x86_64-3.7/MySQLdb/_mysql.cpython-37m-darwin.so ld: library not found for -lssl clang: error: linker command failed with exit code 1 (use -v to see invocation) error: command 'gcc' failed with exit status 1 ---------------------------------------- ERROR: Failed building wheel for mysqlclient The easiest and fastest solution to try is: Update your ~/.bash_profile or ~/.bashrc and add this line: export PATH="/usr/local/opt/openssl/bin:$PATH" Reload ~/.bash_profile or ~/.bashrc source ~/.bash_profile  
  • graphql python graphene

    Exclude fields or set Specific fields on Python Graphene

    I wanted to exclude or set specific fields and tried to follow the official documentation but always get TypeError: __init_subclass_with_meta__() got an unexpected keyword argument 'fields' or  TypeError: __init_subclass_with_meta__() got an unexpected keyword argument 'exclude' I've spent hours trying to figure things out and I'm really surprised that it seems that I'm the only one that have this problem. I have finally, found the solution and I wanted to share this so just in case there's someone out there that needs this, hope you find this answer and might save you some hours.   fields Show only these fields on the model: class QuestionType(DjangoObjectType):     class Meta:         model = Question         only_fields = ('id', 'question_text')   exclude Show all fields except those in exclude: class QuestionType(DjangoObjectType):     class Meta:         model = Question         exclude_fields = ('question_text',)   ... and that's it! If you have any idea what happened with my issues or any better solution, please leave it in the comments ;)
  • pip package

    How to upgrade all Python packages using pip

    There's no built-in command or parameter yet, but you can use:  pip list --outdated --format=freeze | grep -v '^\-e' | cut -d = -f 1 | xargs -n1 pip install -U In older version of pip, you can use:  pip freeze --local | grep -v '^\-e' | cut -d = -f 1 | xargs -n1 pip install -U   There are infinite potential variations for this. I'm trying to keep this answer short and simple, but please suggest variations in the comments!
  • Change Virtual Environment Python Version

    Remove existing virtual environment Python files cd [EXISTING_ENV_PATH] rm .Python rm bin/pip{,2,2.7} rm bin/python{,2,2.7} rm -r include/python2.7 rm lib/python2.7/* rm -r lib/python2.7/distutils rm lib/python2.7/site-packages/easy_install.* rm -r lib/python2.7/site-packages/pip rm -r lib/python2.7/site-packages/pip-*.dist-info rm -r lib/python2.7/site-packages/setuptools rm -r lib/python2.7/site-packages/setuptools-*.dist-info Run the commands above to delete all python files (assuming that the old python version is python 2). Initialise Virtual Environment with new Python version virtualenv -p `which python3` . Run the command above inside the existing environment path.
  • Fix Django Rest Framework Tracking (drf-tracking) errors with Django model FileField

    I was working recently on logging Django Rest Framework (DRF) requests and responses and also creating a download endpoint so I can protect files from being accessed by other users with the ability log requests to see who's downloading what. So I came across this awesome DRF plugin called drf-tracking. It so easy to use that you just need to add rest_framework_tracking.mixins.LoggingMixin to your existing DRF views like the code below: from rest_framework import generics from rest_framework.response import Response from rest_framework_tracking.mixins import LoggingMixin class LoggingView(LoggingMixin, generics.GenericAPIView): def get(self, request): return Response('with logging') And you should see the logs in your django admin page. Upon testing, I noticed that I get an exception when I'm sending a POST request with a file and also have the decoding error on my download api. So I have to override the mixin methods. from rest_framework import viewsets, renderers from rest_framework.response import Response from rest_framework.decorators import detail_route from django.http import FileResponse, HttpResponse from rest_framework_tracking.mixins import LoggingMixin from .models import File class BinaryFileRenderer(renderers.BaseRenderer): media_type = 'application/octet-stream' format = None charset = None render_style = 'binary' def render(self, data, media_type=None, renderer_context=None): return data class FileViewSet(LoggingMixin, viewsets.ModelViewSet): queryset = File.objects.all() # Override finalize_response to check if response is FileResponse then I will return the rendered_content response without # processing it to avoid decode exceptions def finalize_response(self, request, response, *args, **kwargs): if response.__class__.__name__ == 'FileResponse': mixinResponse = super(FileViewSet, self).finalize_response(request, response, *args, **kwargs) if hasattr(response, 'rendered_content'): rendered_content = response.rendered_content else: rendered_content = response.getvalue() return HttpResponse(rendered_content) return super(FileViewSet, self).finalize_response(request, response, *args, **kwargs) # Override _clean_data to decode data with ignore instead of replace to ignore # errors in decode so when the logger inserts the data to db, it will not hit # any decoding/encoding issues def _clean_data(self, data): if isinstance(data, bytes): data = data.decode(errors='ignore') return super(FileViewSet, self)._clean_data(data) @detail_route(methods=['get'], renderer_classes=(BinaryFileRenderer,)) def download(self, request, pk=None): queryset = File.objects.get(id=pk) documentFile = queryset.document file_handle = documentFile.open() # send file response = FileResponse(file_handle) return response  
  • django rest framework

    Saving foreign key ID with Django REST framework serializer

    If you are using Django REST framework on serving your APIs, you probably did the below code in returning the related object in your serializer. class MyTableSerializer(serializers.ModelSerializer): user = UserSerializer(many=False, read_only=True) class Meta: fields = '__all__' model = MyTable But doing this will not allow your API to pass the foreign key id. Instead, you need to include the field name in the serializer like the code below: class MyTableSerializer(serializers.ModelSerializer): user = UserSerializer(many=False, read_only=True) user_id = serializers.IntegerField(write_only=True) class Meta: fields = '__all__' model = MyTable