Combine two django models with shared ids into a single viewset - django

I have two django models in two independent apps, who use the same user ids from an external authentication service:
In app1/models.py:
class App1User(models.Model):
user_id = models.UUIDField(unique=True)
app1_field = models.BooleanField()
In app2/models.py:
class App2User(models.Model):
user_id = models.UUIDField(unique=True)
app2_field = models.BooleanField()
I would like to have a combined viewset that can make it seem like these two are a single model with a list response as follows:
[
{
'user_id': ...,
'app1_field': ...,
'app2_field': ...
},
...
]
If I create or update with this viewset, it should save the data to each of the two models.

To create a combined viewset for App1User and App2User models, you can
follow these steps:
Create a serializer for the combined model. In your main app, create a
serializers.py file and define the following serializer:
from rest_framework import serializers
from app1.models import App1User
from app2.models import App2User
class App1UserSerializer(serializers.ModelSerializer):
class Meta:
model = App1User
fields = ('user_id', 'app1_field')
class App2UserSerializer(serializers.ModelSerializer):
class Meta:
model = App2User
fields = ('user_id', 'app2_field')
class CombinedUserSerializer(serializers.Serializer):
user_id = serializers.UUIDField()
app1_field = serializers.BooleanField()
app2_field = serializers.BooleanField()
def create(self, validated_data):
app1_data = {'user_id': validated_data['user_id'], 'app1_field': validated_data['app1_field']}
app2_data = {'user_id': validated_data['user_id'], 'app2_field': validated_data['app2_field']}
app1_serializer = App1UserSerializer(data=app1_data)
app2_serializer = App2UserSerializer(data=app2_data)
if app1_serializer.is_valid() and app2_serializer.is_valid():
app1_serializer.save()
app2_serializer.save()
return validated_data
def update(self, instance, validated_data):
app1_instance = App1User.objects.get(user_id=validated_data['user_id'])
app2_instance = App2User.objects.get(user_id=validated_data['user_id'])
app1_serializer = App1UserSerializer(app1_instance, data={'app1_field': validated_data['app1_field']})
app2_serializer = App2UserSerializer(app2_instance, data={'app2_field': validated_data['app2_field']})
if app1_serializer.is_valid() and app2_serializer.is_valid():
app1_serializer.save()
app2_serializer.save()
return validated_data
This serializer defines the structure of the combined model and uses App1UserSerializer and App2UserSerializer to handle the fields of each model.
Create a viewset for the combined model. In your main app, create a viewsets.py file and define the following viewset:
from rest_framework import viewsets
from app1.models import App1User
from app2.models import App2User
from .serializers import CombinedUserSerializer
class CombinedUserViewSet(viewsets.ModelViewSet):
serializer_class = CombinedUserSerializer
def get_queryset(self):
app1_users = App1User.objects.all()
app2_users = App2User.objects.all()
combined_users = []
for app1_user in app1_users:
app2_user = app2_users.filter(user_id=app1_user.user_id).first()
if app2_user:
combined_user = {
'user_id': app1_user.user_id,
'app1_field': app1_user.app1_field,
'app2_field': app2_user.app2_field
}
combined_users.append(combined_user)
return combined_users
This viewset uses CombinedUserSerializer to handle the requests and retrieves the data from both App1User and App2User models. The get_queryset() method retrieves all the App1User and App2User instances, matches them based on their user_id, and creates a list of combined users.
Register the viewset in your main app's urls.py file:
# main_app/urls.py
from django.urls import include, path
from rest_framework import routers
from .views import CombinedUserViewSet
router = routers.DefaultRouter()
router.register(r'combined-users', CombinedUserViewSet)
urlpatterns = [
path('', include(router.urls)),
]
This code sets up a default router and registers the CombinedUserViewSet with the endpoint /combined-users/, which can be used to access the combined model data.
Above solution is the idea how it can be done, you can modify according to your needs. If this idea helps, plz mark this as accept solution.

Related

Django Rest Framework: Get Data by Field

i want to learn django.
My first learning project is a django + rest framework api.
i want to get a destination by its airport code. not by pk / id
currently when i call /api/destination/1 i get the destination with id 1
i want something like /api/destination/PMI or /api/destination/mallorca and as response i only want to get the destination with code PMI or with name mallorca.
is this possible?
my files:
modely.py
class Destination(models.Model):
name = models.CharField(max_length=50)
code = models.CharField(max_length=3)
country = models.CharField(max_length=50)
image = models.FileField()
serializers.py
class DestinationSerializer(serializers.ModelSerializer):
class Meta:
model = Destination
fields = ("id", "name", "code", "country", "image")
urls.py
router = DefaultRouter()
router.register(r'destination', DestinationViewSet)
views.py
class DestinationViewSet(viewsets.ModelViewSet):
serializer_class = DestinationSerializer
queryset = Destination.objects.all()
I would recommend picking one or the other to have as the identifier. For this example, I'm going to use the airport code.
In urls.py, you'll want to switch from a router to a urlpattern - remember to register this in your project.urls file!
from django.urls import path
urlpatterns = [path('destination/<code>/', DestinationViewSet.as_view())]
In your view, you'll want to switch to just a normal view and call the get() method.
from destinations.api.serializers import DestinationSerializer
from destinations.models import Destination
from rest_framework import views
from rest_framework.response import Response
class DestinationView(views.APIView):
def get(self, request, code):
destination = Destination.objects.filter(code=code)
if destination:
serializer = DestinationSerializer(destination, many=True)
return Response(status=200, data=serializer.data)
return Response(status=400, data={'Destination Not Found'})
Everything else should work the way it is!
Use an action decorator to create a custom get method
#action(detail=False, methods=['GET'], url_path='destination/(?P<pmi>\w{0,500})')
def custom_ge(self, request, pmi):
#Function implementation in here

django rest framework not showing response data

i have a problem with django rest framework and mySql the response data not showing:
i define serializer:
from rest_framework import serializers
from ..models import H010002
class H010002Serializer(serializers.Serializer):
class Meta:
model = H010002
# Note: any fild passed her will send to xhr request
fields = [
'id',
'module_name',
'module_description',
'module_type',
]
2- view.py
from .serializers import H010002Serializer
from ..models import H010002
from rest_framework.generics import (
ListAPIView,
RetrieveAPIView,
)
# Create your views here.
class H010002ListAPIView(ListAPIView):
queryset = H010002.objects.all()
serializer_class = H010002Serializer
3- data base included 1 record
4- django rest framework disply empty data with status 200 OK
Note: if i use normal class ListView the data come correctly
You should use serializers.ModelSerializer class to create the serializer.
class H010002Serializer(serializers.ModelSerializer):
class Meta:
model = H010002
fields = ['id', 'module_name', 'module_description', 'module_type', ]

How to accept form data and return it along with the results of some processing in Django RESTFramework?

I am trying to understand Django RESTFramework. I am already familiar with Django. I want to create an endpoint that accepts some text data and processes it and returns it to the user along with the results of the processing (in text). I have completed a couple of tutorials on the topic but I still don't understand how it works. Here is an example from a working tutorial project. How can I edit it to achieve my goal? It all looks automagical.
# views.py
from rest_framework import generics
from .models import Snippet
from .serializers import SnippetSerializer
class SnippetList(generics.ListCreateAPIView):
queryset = Snippet.objects.all()
serializer_class = SnippetSerializer
​
class SnippetDetail(generics.RetrieveUpdateDestroyAPIView):
# Here I would like to accept form data and process it before returning it along with the
# results of the processing.
queryset = Snippet.objects.all()
serializer_class = SnippetSerializer
Okay, I think you are a newbie in Django rest and try to understand its flow so I can explain it with an example of a subscription plan.
First, create a model in models.py file
from django.db import models
class SubscriptionPlan(models.Model):
plan_name = models.CharField(max_length=255)
monthly_price = models.IntegerField()
yearly_price = models.IntegerField()
Then create views in a view.py file like
from rest_framework.views import APIView
class SubscriptionCreateAPIView(APIView):
serializer_class = SubscriptionSerializer
def post(self, request):
serializer = self.serializer_class(data=request.data)
serializer.is_valid(raise_exception=True)
serializer.save()
return Response(
{'message': 'Subscription plan created successfully.',
'data': serializer.data},
status=status.HTTP_201_CREATED
)
and then define a serializer for validation and fields in which we can verify which fields will be included in the request and response object.
serializers.py
from rest_framework import serializers
from .models import SubscriptionPlan
class SubscriptionSerializer(serializers.ModelSerializer):
plan_name = serializers.CharField(max_length=255)
monthly_price = serializers.IntegerField(required=True)
yearly_price = serializers.IntegerField(required=True)
class Meta:
model = SubscriptionPlan
fields = (
'plan_name', 'monthly_price', 'yearly_price',
)
def create(self, validated_data):
return SubscriptionPlan.objects.create(**validated_data)
Now add urls in src/subsciption_module/urls.py
from django.urls import path
from .views import SubscriptionCreateAPIView
app_name = 'subscription_plan'
urlpatterns = [
path('subscription_plan/', SubscriptionCreateAPIView.as_view()),
]
At the end include module url in root urls.py file where your main urls will be located. It will be the same directory which contains settings.py and wsgi.py files.
src/urls.py
from django.contrib import admin
from django.urls import path, include
urlpatterns = [
path('admin/', admin.site.urls),
path('api/v1/', include('src.subscription_plan.urls', namespace='subscription_plan')),
]
That's it. This is how flow works in django rest and you can process data and display data in this way. For more details you can refer django rest docs.
But this is not in any way different from what you do with plain Django. Your SnippetDetail view is just a class-based view, and like any class-based view if you want to do anything specific you override the relevant method. In your case, you probably want to override update() to do your custom logic when receiving a PUT request to update data.

Can't create HyperlinkedRelatedField in Django Rest Framework

I'm struggling to get HyperlinkedRelated fields working, and I can't figure out where I'm going wrong. No matter what I do, I get an error like:
Could not resolve URL for hyperlinked relationship using view name "court-detail". You may have failed to include the related model in your API, or incorrectly configured the lookup_field attribute on this field.
I feel that I've tried everything and I don't know where my error could be, nor how to identify it. My reading of this error message is that I am looking for a URL that corresponds to the court-detail view, but that that view doesn't exist.
Some piece of magic isn't working and any help would be greatly appreciated.
URLs:
from cl.api import views
from django.conf.urls import url, include
from rest_framework.routers import DefaultRouter
router = DefaultRouter()
router.register(r'courts', views.CourtViewSet)
router.register(r'dockets', views.DocketViewSet)
router.register(r'clusters', views.OpinionClusterViewSet)
router.register(r'opinions', views.OpinionViewSet)
router.register(r'cited-by', views.OpinionsCitedViewSet)
urlpatterns = [
# url(r'^api/rest/(?P<version>[v3]+)/', include(router.urls)),
url(r'^api-auth/',
include('rest_framework.urls', namespace='rest_framework')),
url(r'^', include(router.urls)),
]
Views:
class DocketViewSet(viewsets.ModelViewSet):
queryset = Docket.objects.all()
serializer_class = serializers.DocketSerializer
class CourtViewSet(viewsets.ModelViewSet):
queryset = Court.objects.all()
serializer_class = serializers.CourtSerializer
class OpinionClusterViewSet(viewsets.ModelViewSet):
"""
This viewset automatically provides `list`, `create`, `retrieve`,
`update` and `destroy` actions.
"""
queryset = OpinionCluster.objects.all()
serializer_class = serializers.OpinionClusterSerializer
class OpinionViewSet(viewsets.ModelViewSet):
queryset = Opinion.objects.all()
serializer_class = serializers.OpinionSerializer
class OpinionsCitedViewSet(viewsets.ModelViewSet):
queryset = OpinionsCited.objects.all()
serializer_class = serializers.OpinionsCitedSerializer
Serializers:
from cl.audio import models as audio_models
from cl.search import models as search_models
from rest_framework import serializers
class DocketSerializer(serializers.HyperlinkedModelSerializer):
court = serializers.HyperlinkedRelatedField(
many=False,
view_name='court-detail',
read_only=True
)
class Meta:
model = search_models.Docket
fields = ('date_created', 'date_modified', 'date_argued',
'date_reargued', 'date_reargument_denied', 'court')
class CourtSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = search_models.Court
class AudioSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = audio_models.Audio
class OpinionClusterSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = search_models.OpinionCluster
fields = ('judges', 'per_curiam', )
class OpinionSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = search_models.Opinion
fields = ('pk',)
class OpinionsCitedSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = search_models.OpinionsCited
When I go to:
http://127.0.0.1:8000/dockets/
It tells gives me the error message above. Of course, if I remove the court reference from the serialization, it works fine...
I imagine this can be caused by a number of things, but in my case, I figured out that it was caused by having DEFAULT_VERSIONING_CLASS set without having it configured in the urls.py:
REST_FRAMEWORK = {
# Use URL-based versioning
'DEFAULT_VERSIONING_CLASS': 'rest_framework.versioning.URLPathVersioning',
'DEFAULT_VERSION': 'v3',
'ALLOWED_VERSIONS': {'v3'},
}
The solution, therefore, was either to disable it in the settings, or to set up a url in in urls.py that accepted the version parameter:
url(r'^api/rest/(?P<version>[v3]+)/', include(router.urls)),
Ugh. Took a long time to realize I had this setting in place. Bad error message.

how to use django rest filtering with mongoengine

Hi I am starting django 1.8.3 with mongodb using mongo engine to create rest api.
I am using rest_framework_mongoengine to do so.
I wanted to use a feature of DjangoFilterBackend for same.
My code is:
models.py:
from mongoengine import *
from django.conf import settings
connect(settings.DBNAME)
class Client(Document):
name = StringField(max_length=50)
city = StringField(max_length=50)
country = StringField(max_length=200, verbose_name="Country")
address = StringField(default='')
Serializer.py
from client.models import Client
from rest_framework_mongoengine.serializers import DocumentSerializer
class ClientSerializer(DocumentSerializer):
class Meta:
model = Client
depth = 1
views.py
from rest_framework_mongoengine.generics import *
from rest_framework import filters
class ClientList(ListCreateAPIView):
serializer_class = ClientSerializer
queryset = Client.objects.all()
filter_backends = (filters.DjangoFilterBackend,)
filter_fields = ('name',)
I start getting error
QuerySet object has no attribute model
Don't know where its going wrong. If I remove filter_field It works but I can not use filter feature.
Any help would be of great use
You can also perform the filtering by overriding the get_queryset() method and creating a generic filtering function.
Here, we specify the filter fields tuple in the view as my_filter_fields on which we want to perform filtering. Then in our get_queryset(), we call a function get_kwargs_for_filtering().
get_kwargs_for_filtering() functions iterates over the fields defined in my_filter_fields and checks if this was passed in the query_params. If the field is found then a key with the field name and value as the retrieved value is set in a dictionary filtering_kwargs. After the iteration is over, this filtering_kwargs dictionary is returned to the get_queryset() method.
This filtering_kwargs dictionary is used to filter the queryset then.
from rest_framework_mongoengine.generics import *
from rest_framework import filters
class ClientList(ListCreateAPIView):
serializer_class = ClientSerializer
my_filter_fields = ('name', 'country') # specify the fields on which you want to filter
def get_kwargs_for_filtering(self):
filtering_kwargs = {}
for field in self.my_filter_fields: # iterate over the filter fields
field_value = self.request.query_params.get(field) # get the value of a field from request query parameter
if field_value:
# filtering_kwargs[field] = field_value
field = self.get_serializer().fields[field_name]
filtering_kwargs[field] = field.to_representation(field_value)
return filtering_kwargs
def get_queryset(self):
queryset = Client.objects.all()
filtering_kwargs = self.get_kwargs_for_filtering() # get the fields with values for filtering
if filtering_kwargs
queryset = Client.objects.filter(**filtering_kwargs) # filter the queryset based on 'filtering_kwargs'
return queryset
here is a good method to do this work.
firstly, you should install django_mongoengine_filter rest_framework_mongoengine
pip install django_mongoengine_filter
pip install rest_framework_mongoengine
secondly, you can write your owner Filter like this:
import django_mongoengine_filter as filters
from app.models import User
class UserFilter(filters.FilterSet):
class Meta:
model = User
fields = ['name']
finally, you can use the UserFilter like this in view class:
from app.models import User
from app.serializer import UserS # use rest_framework_mongoengine to write serializer
from app.filters import UserFilter
class UserVS(ModelViewSet):
queryset = User.objects.all()
serializer_class = UserS
# override filter_queryset function
def filter_queryset(self, queryset):
filter = UserFilter(self.request.query_params, queryset=queryset)
return filter.qs
Although the question is referring to Django 1.8. This is a question that is still important and valid in later versions of Django, DRF and MongoEngine.
At the time of this answer I am running
Django==4.1.3
djangorestframework==3.14
django-rest-framework-mongoengine==3.4.1
mongoengine==0.24.2
django-filter==22.1
# patched version of django-mongoengine-filter to support Django 4.0
# https://github.com/oussjarrousse/django-mongoengine-filter
# Pull request https://github.com/barseghyanartur/django-mongoengine-filter/pull/16
django-mongoengine-filter>=0.3.5
The idea in this answer is to add filtering support to django-rest-framework-mongoengine using django-mongoengine-filter that is an replacement or an extension to django-filter and should work the same way as django-filter.
First let's edit the project/settings.py file. Find the INSTALLED_APPS variable and make sure the following "Django apps" are added:
# in settings.py:
INSTALLED_APPS = [
# ...,
"rest_framework",
"rest_framework_mongoengine",
"django_filters",
# ...,
]
the app django_filters is required to add classes related to filtering infrastructure, and other things including html templates for DRF.
Then in the variable REST_FRAMEWORK we need to edit the values associated with the key: DEFAULT_FILTER_BACKENDS
# in settings.py:
REST_FRAMEWORK = {
# ...
"DEFAULT_FILTER_BACKENDS": [
"filters.DjangoMongoEngineFilterBackend",
# ...
],
# ...
}
DjangoMongoEngineFilterBackend is a custom built filter backend that we need to add to the folder (depending on how you structure your project) in the file filters
# in filters.py:
from django_filters.rest_framework.backends import DjangoFilterBackend
class DjangoMongoEngineFilterBackend(DjangoFilterBackend):
# filterset_base = django_mongoengine_filter.FilterSet
"""
Patching the DjangoFilterBackend to allow for MongoEngine support
"""
def get_filterset_class(self, view, queryset=None):
"""
Return the `FilterSet` class used to filter the queryset.
"""
filterset_class = getattr(view, "filterset_class", None)
filterset_fields = getattr(view, "filterset_fields", None)
if filterset_class:
filterset_model = filterset_class._meta.model
# FilterSets do not need to specify a Meta class
if filterset_model and queryset is not None:
element = queryset.first()
if element:
queryset_model = element.__class__
assert issubclass(
queryset_model, filterset_model
), "FilterSet model %s does not match queryset model %s" % (
filterset_model,
str(queryset_model),
)
return filterset_class
if filterset_fields and queryset is not None:
MetaBase = getattr(self.filterset_base, "Meta", object)
element = queryset.first()
if element:
queryset_model = element.__class__
class AutoFilterSet(self.filterset_base):
class Meta(MetaBase):
model = queryset_model
fields = filterset_fields
return AutoFilterSet
return None
This custom filter backend will not raise the exceptions that the original django-filter filter backend would raise. The django-filter DjangoFilterBackend access the key model in QuerySet as in queryset.model, however that key does not exist in MongoEngine.
Maybe making it available in MongoEngine should be considered:
https://github.com/MongoEngine/mongoengine/issues/2707
https://github.com/umutbozkurt/django-rest-framework-mongoengine/issues/294
Now we can add a custom filter to the ViewSet:
# in views.py
from rest_framework_mongoengine.viewsets import ModelViewSet
class MyModelViewSet(ModelViewSet):
serializer_class = MyModelSerializer
filter_fields = ["a_string_field", "a_boolean_field"]
filterset_class = MyModelFilter
def get_queryset(self):
queryset = MyModel.objects.all()
return queryset
Finally let's get back to filters.py and add the MyModelFilter
# in filters.py
from django_mongoengine_filter import FilterSet, StringField, BooleanField
class MyModelFilter(FilterSet):
"""
MyModelFilter is a FilterSet that is designed to work with the django-filter.
However the original django-mongoengine-filter is outdated and is causing some troubles
with Django>=4.0.
"""
class Meta:
model = MyModel
fields = [
"a_string_field",
"a_boolean_field",
]
a_string_field = StringFilter()
a_boolean_field = BooleanFilter()
That should do the trick.