Serialize available choices and mark selected - django-rest-framework

I am new to REST and django-rest-framework. I want to get list of available ManyToMany choices along with some way to know which ones are currently selected.
I have model like this:
class PGroup(models.Model):
.
permissions = models.ManyToManyField(
Permission, related_name="group_permissions", help_text=_('Select permissions for this group.')
)
Serializers.
class PermissionSerializer(serializers.ModelSerializer):
class Meta:
model = Permission
fields = ['pk', 'name',]
class PGroupSerializer(serializers.ModelSerializer):
permissions = PermissionSerializer(many=True)
class Meta:
model = PGroup
fields = [....'permissions']
Looking at Browseable API, with this setup I get 'permissions: []'(empty list) for generics.createAPIView and get the associated 'permissions[....]'(non-empty list) for generics.RetrieveUpdateAPIView.
I want a list of available permissions on both API views and also want to know which permissions are already selected for Update API view.
Can anyone please help.
Thanks

There are 2 ways to get the list of choices.
Using the SerializerMethodField,
from rest_framework import serializers
from .models import Permission
class PGroupSerializer(serializers.ModelSerializer):
permissions = PermissionSerializer(many=True)
all_available_permissions = serializers.SerializerMethodField()
def get_all_available_permissions(self, obj):
return Permission.objects.all()
class Meta:
model = PGroup
fields = ['permissions', "all_available_permissions"]
or using source, we can define a custom method on the model and point the serializer to use it using the source argument.
### models.py
class PGroup(models.Model):
.
permissions = models.ManyToManyField(
Permission, related_name="group_permissions", help_text=_('Select permissions for this group.')
)
def all_permissions(self):
return Permission.objects.all()
### serializers.py
class PGroupSerializer(serializers.ModelSerializer):
permissions = PermissionSerializer(many=True)
all_available_permissions = PermissionSerializer(many=True, read_only=True, source="all_permissions")
class Meta:
model = PGroup
fields = ['permissions', "all_available_permissions"]
2nd option is much better, IMO.
Note: you may not always want to send a full list of choices as that could get really slow overtime when u have hundreds or thousands of objects.

Related

How to get the current logged-in user for schema? (django graphql api)

my goal: I am trying to return all the fields of the posts if the user has an id of 1, and I want to return only 3 fields if else.
My problem is: in the query or mutations I can do info.context.user.id but in the schema I can't do that.
Here in my following code you can noticed his undefined variable current_loggedin_user which I don't know how to get its value.
import graphene
from graphene_django import DjangoObjectType
from . import models
from django.conf import settings
class Posts(DjangoObjectType):
class Meta:
model = models.ExtendUser
if (current_logedin_user.id==1):
field = '__all_'
else:
fields = ['username', 'id', 'imageUrl']
You need to include all fields that are visible to anyone to the schema, and then customize the resolver methods for the fields that you want to hide for some users. For example:
class Posts(DjangoObjectType):
class Meta:
model = models.ExtendUser
def resolve_restricted_field(self, info):
if info.context.user.id == 1:
return self.restricted_field
return None
For more examples, see How to limit field access on a model based on user type on Graphene/Django?
Try something like this
class Posts(DjangoObjectType):
class Meta:
model = models.ExtendUser
class Query(ObjectType):
fields = graphene.List(Posts)
def resolve_fields(self, info, **kwargs):
if info.context.user.pk == 1:
return ExtendUser.objects.all()
return ExtendUser.objects.values_list('username', 'id', 'imageUrl', flat=True)

How to write a generic CRUD service on django using rest framework?

I am new to django and faced to several problems trying to write a simple service.
What am I trying to do?
I intend to write a generic crud service for my models using rest-framework library.
I don't want to write serializers and views to all my models and trying to optimize code and learn some useful stuff.
My model
Let's imagine I have an abstract BaseBusinessObject
class BaseBusinessObject(models.Model):
CreatedAt = models.DateField()
UpdatedAt = models.DateField()
class Meta:
abstract = True
I also have a plenty of concrete classes, which are inherited from base one:
class Product(BaseBusinessObject):
Description: models.TextField()
Type: models.CharField()
....
class Company(BaseBusinessObject):
Title: models.CharField()
....
class Person(BaseBusinessObject):
Name: models.CharField()
and so on
What I want
I already figured out, that with rest-framework I can create serializers and views, then register router for url .../Product, .../Company, .../Person. But what if I have 1000 classes? This is boring
A. How can I dynamically specified url's for child objects? I don't want to hardcode methods, I am looking for solution...something like this:
.../api/Entities/ClassName
B. How can I then use my dynamically created urls in django-rest-framework?
router.register('persons', PersonViewSet)
How can write it in more generic way?
router.register('<ClassName>', <GenericViewSet>)
C. Using DRF I can create my viewset for each concrete class in my model:
class PersonViewSet(viewsets.ModelViewSet):
queryset = Person.objects.all()
serializer_class = PersonSerializer
But as I said I have a lot of classes. How can I write it in more generic way?
I tried to create a view set for abstract class, but there are some trouble when querying an abstract object.
Is it possible to create such service for an abstract class and then all its child simply(or not simply) inherit CRUD methods?
Maybe I should try to write a factory for serializers and viewsets?
What possible solutions could I implement for solving my problem?
After 2 days of walking around I finally find my solution. May be someone else will face the some problem, so I trying to explain what I had already done.
First, I create a "base" application inside my django project and add it to settings.py. Then I create an abstract class in models.py:
class BaseCrudEntity(models.Model):
pass
class Meta:
abstract = True
I want to write a generic service for CRUD operations for all "business" classes.
The problem is that I don't want to write serializers and views for them - I want to create them "on fly", dynamically. I decided to use django rest framework as well, because I am not intended to create a bycicle again.
I decided to inherit all my "business" classes from that abstract one and write a service for all possible "families"
So I have to create a fabric which is responsible for VeiwSet creation.
Here is my view.py:
class BaseCrudViewSetFabric():
#classmethod
def CreateViewSet(self, _context):
classname = _context.__name__ + 'ViewSet'
return type(classname, (viewsets.ModelViewSet,), {
'queryset':_context.objects.all(),
'serializer_class':BaseCrudSerializerFabric.CreateSrializer(_context)
})
pass
here _context - variable which describes concrete class of my model.
as you can see this function creates a concrete ViewSet based on my context. Inside it a Serializers fabric is called.
Here the code of my serializers.py:
class BaseCrudSerializerFabric():
#classmethod
def CreateSrializer(self, _context):
classname = _context.__name__
_Meta = type('Meta', (), {'model':_context,'fields':'__all__'})
_crudserializer = type(
classname,
(serializers.ModelSerializer,),
{'Meta': _Meta}
)
return _crudserializer
Moreover, I have to write a service for dynamically routing - I don't wanna hardcode my urls.
Here the example ursl.py from core project:
from base.urls import router
url(r'^api/v1/', include(router.urls))
and from base/urls.py:
from rest_framework.routers import DefaultRouter, SimpleRouter
from base.models.BaseCrudEntity import BaseCrudEntity
from base.views.basecrud_view import BaseCrudViewSetFabric
class CustomRouter(SimpleRouter):
def RoutRegister(self):
childs = getChilds(BaseCrudEntity)
#print(childs)
for ch in childs:
if (ch._meta.abstract == False):
#print(ch.__name__)
prefix = ch.__name__
self.register(prefix, BaseCrudViewSetFabric.CreateViewSet(ch))
return(self)
pass
router = CustomRouter()
router.RoutRegister()
Finally I simply create some concrete models:
from django.db import models
from base.models.BaseCrudEntity import BaseCrudEntity
class Person(BaseCrudEntity):
Name = models.CharField(max_length = 255)
Surname = models.CharField(max_length = 255)
Patronymic = models.CharField(max_length = 255, null = True)
DateOfBirth = models.DateField(null = True)
#slug = models.SlugField(default = 'hui', editable = False)
def __str__(self):
return "{} {} {}".format (self.Surname, self.Name, self.Patronymic)
and thats all.
When application starts it register a route for http://127.0.0.1:8000/api/v1/Person and creates serializers and viewsets so all CRUD operations provided by Django- Rest Framework will be provided as well.
I would suggest using query parameters instead of path parameters, then you just register one URL and process the various elements of the request and route it correctly server-side. When do I use path params vs. query params in a RESTful API?

Django rest framework - Raising exception / Handling empty results while filtering

I have a user profile class and am checking if a user exists and if not want to create that user.
Am using the filter class for userprofile so that the client can call :
http://localhost:8000/users/?email=a#b.com
and if the result is empty will create a user with the email address.
Is there a way to intercept the query result and raise an exception when its empty and handle that to create the user.
If there is a better way would like to be corrected as well.
class UserQueryFilter(django_filters.rest_framework.FilterSet):
email = django_filters.CharFilter(name="user__email")
username = django_filters.CharFilter(name="user__username")
class Meta:
model = UserProfile
fields = ['email', 'username']
class UserViewSet(viewsets.ReadOnlyModelViewSet):
queryset = UserProfile.objects.all()
serializer_class = UserSerializer
filter_class = UserQueryFilter
Any help is appreciated.
Thanks
Anand
Django Rest Framework provide a functionality that is disabled by default. Maybe it could give you another approach to resolve your problem: PUT as create
In other hand, if you really need to create the user through a GET request with a querystring, you can use a MethodFilter from django-filters, for example:
class UserFilters(FilterSet):
user = MethodFilter(action='filter_user')
class Meta:
model = User
fields = ['user']
def filter_user(self, queryset, value):
if not value:
# Here Raise Exception
else:
# Check if the user exists, if not create it
users = queryset.filter(Q(username=value) | Q(email=value))
if not users.exists:
user = User.objects.create(...)
return queryset.filter(pk=user.id)
else:
return users
Hope this can help you. I'm not pretty sure about it works in that exact way but it's the idea.
Personally, I recommend you that try to execute that tasks through a more appropriate request like POST or PUT and manage in the corresponding method.

How do I serialize indirect relationships with querysets?

I have built a Django REST application to serve as backend API For an iOS project. In my object model I use 'Subscription' to join 'User' objects with 'Workspace' objects. Here's a part of my models.py simplified:
class User(models.Model):
# some property fields
class Workspace(models.Model):
# some property fields
class Subscription(models.Model):
# some property fields
user = models.ForeignKey(
User,
on_delete=models.CASCADE,
related_name='subscriptions')
workspace = models.ForeignKey(
Workspace,
on_delete=models.CASCADE,
related_name='subscriptions')
I have built class-based views for the objects so I can get a list of workspace objects with http GET from my iOS front end. For convenience reasons I want to include more than just the model fields, for example in the list of workspaces i want to include a list of subscribed users for every workspace object. I was advised to use SerializerMethodField() and querysets for serializing the field, but I don't know how to construct the queries. I've got this far:
class WorkspaceSerializer(serializers.ModelSerializer):
subscribed_users = serializers.SerializerMethodField()
class Meta:
model = Workspace
fields = ('id', 'subscribed_users')
def get_users(self, workspace):
users = User.objects.filter(???)
serializer = UserSerializer(instance=users, many=True)
return serializer.data
Getting subscriptions related to the workspace is easy because they're directly related, but how do I get users that are subscribed to the workspace in question?
The syntax I was looking for was double underscore, called spanning in DRF. For example:
def get_users(self, workspace):
users = User.objects.filter(subscription_set__workspace=workspace)
serializer = UserSerializer(instance=users, many=True)
return serializer.data

Foreign key field disappears in swagger docs after adding depth attribute in Serializer

Whenever I define the depth attribute, the foreign key field from swagger docs in POST section disappears. That seems strange because I required depth = 1 when I want related data in my GET request. So I can not remove this in order to get this related field parameter in the POST section.
Here is the case.
Model:
from django.db import models
from django.conf import settings
# Create your models here.
User = settings.AUTH_USER_MODEL
class Todo(models.Model):
user = models.ForeignKey(User)
title = models.CharField("Title", max_length=255)
completed = models.BooleanField("Completed")
Serializer without depth =1.
from rest_framework import serializers
from models import Todo
class TodoSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = Todo
Swagger output:
Now If I add depth = 1 than Swagger does not display related field.
Let me know if anyone has any clue about this.
Thanks :)
Finally after digging into this, I come up with solution by which we can avoid this issue and achieve the expected solution.
So the solution is "Instead of using depth = 1 attribute we can using related serializer instance it self where it works similar to depth functionality."
Here is tested solution
Model:
from django.db import models
from django.conf import settings
User = settings.AUTH_USER_MODEL
class Todo(models.Model):
user = models.ForeignKey(User)
title = models.CharField("Title", max_length=255)
completed = models.BooleanField("Completed")
Serializer
from rest_framework import serializers
from django.conf import settings
from models import Todo
User = settings.AUTH_USER_MODEL
class UserSerializer(serializers.HyperlinkedSerializer):
class Meta:
model = User
class TodoSerializer(serializers.HyperlinkedModelSerializer):
user = UserSerializer()
class Meta:
model = Todo
fields = ('user', 'title', 'completed')
Swagger Output:
This solution is kind of different approach in order to achieve the required functionality, But still I am expecting an official solution from django-rest-swagger team, Even I have posted the same query on django-rest-swagger github repo here.
One solution is to just don't use depth and override to_representation method of serializer:
class TodoSerializer(serializers.ModelSerializer):
class Meta:
model = Todo
def to_representation(self, instance):
r = super(TodoSerializer, self).to_representation(instance)
r.update({'user': UserSerializer().to_representation(instance.user)})
return r
This way, in post everything will be as it was, and in get when return json of todo then to_representation will be called and will add user to json data.
You need to update your serialzer as follows
class TodoSerializer(serializers.HyperlinkedModelSerializer):
creator = serializers.RelatedField(queryset=User.objects.all())
class Meta:
model = Todo
fields = ("name", "task", "creator")
depth = 1
you need to mentions fields and RelatedField in your serializers

Resources