Show specific user details in Django application instead of all details - python

I am creating my first Django project . I have taken 30,000 values as input and want to show particular values according to primary key .
Code:
class employeesList(APIView):
def get(self,request):
employees1 = employees.objects.all()
with open('tracking_ids.csv') as f:
reader = csv.reader(f)
for row in reader:
_, created = employees.objects.get_or_create(
AWB=row[0],
Pickup_Pincode=row[1],
Drop_Pincode=row[2],
)
serializer = employeesSerializer(employees1 , many=True)
return Response(serializer.data)
def post(self,request):
# employees1 = employees.objects.get(id=request.employees.AWB)
employees1 = employees.objects.all()
serializer = employeesSerializer(employees1 , many=True)
return Response(serializer.data)
If I enter http://127.0.0.1:8000/employees/ in URL , I get all the values . I want the URL to be like http://127.0.0.1:8000/employees/P01001168074 and show values of P01001168074 where P01001168074 is primary ID .
I have read
1:showing the model values of specific user django
2)editing user details in python django rest framework
but they are different
Can it be done and if it can , then how ?

Presuming that you are using Django 2.0 you must configure a path than can capture your parameter as seen in the documentation
from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework.serializers import ModelSerializer
class A(models.Model):
x = models.CharField(max_length=250)
class MySerializer(ModelSerializer):
class Meta:
model = A
fields = ('x',)
class MyListView(APIView):
def get(self, request, *args, **kwargs):
# simply get all the objects, serialize and return the response
elems = A.objects.all()
response_data = MySerializer(elems, many=True).data
return Response(data=response_data)
class MyDetailView(APIView):
def get(self, request, *args, **kwargs):
# we obtain the parameter from the URL
desired_item = kwargs.get("desired_item", None)
# we filter the objects by it and get our instance
element = A.objects.filter(x=desired_item).first()
# serialize the instance and return the response
response_data = MySerializer(element).data
return Response(data=response_data)
# all we have to now is define the paths for the list and the detail views.
urlpatterns = [
path('employees/', MyListView.as_view()),
path('employees/<str:desired_item>', MyDetailView.as_view())
]

One good option is to use a viewset that already includes a list and detail endpoint and requires little to code for a default simple setup
views.py
from rest_framework import viewsets
class EmployeeViewSet(viewsets.ModelViewSet):
serializer_class = EmployeeSerializer
queryset = Employee.objects.all()
urls.py
from rest_framework.routers import SimpleRouter
from views import EmployeeViewSet
router = SimpleRouter()
router.register(r'employees', EmployeeViewSet, base_name='employees')
urlpatterns = router.get_urls()
You can read more about viewsets in the DRF docs

Related

How to use pk1 and pk2 in Django API?

I'm getting trouble when trying to queryset multiple pks (pk and pk2) in my URL.
I want to do something like this:
urlpatterns = [
path('tableau/<int:pk>/liste/<int:pk2>', views.ListeDetail.as_view()),
]
I need a detail view of liste (pk2) contained in a tableau (pk).
So far, here is my class but it does'nt work properly.
class ListeDetail(generics.RetrieveUpdateDestroyAPIView):
serializer_class = ListeSerializer
def get_queryset(self):
queryset = Liste.objects.filter(tableau_id=self.kwargs["pk"])
return queryset
Anyone know how to work with multiple pks in the url ?
urlpatterns = [
path('tableau/<int:tableau_id>/liste/<int:sub_id>', views.ListeDetail.as_view()),
]
Here you are dealing with nested resources, you need provide lookup_url_kwarg to filter the first level of resource.
from rest_framework.generics import get_object_or_404
class ListeDetail(generics.RetrieveUpdateDestroyAPIView):
lookup_url_kwarg = 'tableau_id' # get your tableau_id
serializer_class = ListeSerializer
def get_queryset(self):
queryset = Liste.objects.filter(tableau_id=self.kwargs["tableau_id"])
return queryset
def get_object(self):
sub_id = self.kwargs['sub_id']
# replace subobjects to your `related_name`(reverse) name
obj = get_object_or_404(Liste.objects.get(tableau_id=self.kwargs["tableau_id"]).subobjects.all(), id = sub_id)
return obj
EDIT
override the get_object method you can adjust the detail view behavior

Lookup django model using two or more different keys

First time developing a django application, and am trying to do something somewhat non-standard...
Is there a way to configure a view that will allow a user to look up a certain model by either one of two unique model attributes.
Ideally, both of these URL schemes would be possible
urlpatterns = [
path('api/somemodel/<int:model_id>/', views.SomeModelDetailView.as_view())
path('api/somemodel/<str:model_name>/', views.SomeModelDetailView.as_view())
]
A simplified example model... Both the id and the name are guaranteed to be unique. Also, by convention, my data is entered in such a way that a name will always be a string and never an integer
from django.db import models
class SomeModel(models.Model):
id = models.AutoField(primary_key=True)
name = models.CharField(max_length=100, unique=True)
Currently, I have this working with the following view...
from rest_framework import generics
from rest_framework import status
from rest_framework.response import Response
from . import models
class SomeModelDetailView(generics.RetrieveAPIView):
queryset = models.SomeModel.objects.all()
serializer_class = serializers.SomeModelSerializer
def get(self, request, model_name=None, model_id=None, format=None):
field = None
key = None
try:
if model_id:
field = "model_id"
key = model_id
m = models.SomeModel.objects.get(id=model_id)
elif model_name:
field = "model_name"
key = model_name
m = models.SomeModel.objects.get(name=model_name)
else:
return Response("Neither model_id nor model_name were provided", status=status.HTTP_400_BAD_REQUEST)
except models.SomeModel.DoesNotExist:
return Response("Unknown {field}: {key}".format(field=field, key=key), status=status.HTTP_400_BAD_REQUEST)
serializer_class = self.get_serializer_class()
serializer = serializer_class(m)
return Response(serializer.data)
However, I am wondering if there is a better way that fits more into a ViewSet/Router (or other) DRF mechanic.
Any ideas?
I think both existing answers (Don's and changak's) are very informative... however I wanted to take it a step further.
This is what I ended up with - it is inspired from Changak's answer however is slightly more generic
class MultiKeyGetObject(generics.GenericAPIView):
def __init__(self):
if not hasattr(self, 'lookup_fields'):
raise AssertionError("Expected view {} to have `.lookup_fields` attribute".format(self.__class__.__name__))
def get_object(self):
for field in self.lookup_fields:
if field in self.kwargs:
self.lookup_field = field
break
else:
raise AssertionError(
'Expected view %s to be called with one of the lookup_fields: %s' %
(self.__class__.__name__, self.lookup_fields))
return super().get_object()
I also loved learning about Q objects from Don - I can imagine a use case where you would want to retrieve objects using ALL of the lookup fields (either an AND or an OR). I feel this is getting into filter territory, however it may be useful...
from functools import reduce
from operator import or_
from rest_framework.generics import get_object_or_404
def get_object(self):
query = reduce(or_, [Q(**{field: self.kwargs[field]}) for field in self.lookup_fields if field in self.kwargs])
obj = get_object_or_404(self.get_queryset(), query)
self.check_object_permissions(self.request, obj)
return obj
Both of the above methods can then be used by a view such as...
class SomeObjectDetailAPIView(MultiKeyGetObject, generics.RetrieveUpdateDestroyAPIView):
serializer_class = serializers.SomeModelSerializer
queryset = models.SomeModel.objects.all()
lookup_fields = ('id', 'name')
I believe you can do that in a generic way, first change the urlpttern to this:
urlpatterns = [
path('api/somemodel/<str:pk>/', views.SomeModelDetailView.as_view()),
] ### this path matches both of the keys you wanted str and integer(integer is a str too)
then in the view you only need to override the get_object() in this way(don't override get() that is not the drf way):
from rest_framework import generics
from rest_framework import status
from rest_framework.response import Response
from . import models
class SomeModelDetailView(generics.RetrieveAPIView):
queryset = models.SomeModel.objects.all()
serializer_class = serializers.SomeModelSerializer
lookup_url_kwarg = 'pk'
def get_object(self):
pk = self.kwargs[self.lookup_url_kwarg] ## first get value the url parameter(pk)
### then here convert its type to int if it's an integer,
### it's not a bad thing, path() will have done this if we specify its type `int` in the url
try:
self.kwargs[self.lookup_url_kwarg] = int(pk)
self.lookup_field = 'id' ### change the lookup field to 'id' if it's an integer
except:
self.lookup_field = 'name' ### change the lookup field to 'name' if it's a str
return super(SomeModelDetailView, self).get_object() ## finally call the super get_object
This is possible using what Django calls "Q objects". These allow you to perform logical operations to queries allowing you to query for id=model_id or name=model_name.
For example:
from django.db.models import Q
...
m = models.SomeModel.objects.get(Q(id=model_id) | Q(name=model_name))
...

How to expose Django-Constance settings via Django Rest Framework?

I am building a project in which I need to have a few custom settings that are editable by certain users which do not have access to the django admin panel, who access the application via an Angular frontend.
I installed django-constance in order to have these custom settings editable, as it was the most recommended package I could find on the Django packages site. My problem now is that I do not know how to do to serve the settings on my API rest.
I started with some customized view:
class ConstanceSettingSerializer(serializers.Serializer):
name = serializers.CharField()
value = serializers.Field()
defult_value = serializers.Field()
modified = serializers.BooleanField(read_only=True)
from constance import settings as constance_settings
class ConstanceView(APIView):
def get(self, request):
settings = constance_settings.CONFIG.items()
return Response(settings, status=status.HTTP_200_OK)
But now I dont know how to pass those settings correctly to my serializer. I tried this without success:
settings_serializer = ConstanceSettingSerializer(settings)
return Response(settings_serializer.data, status=status.HTTP_200_OK)
And I am only getting errors.
Some idea how to do this properly?
Thanks!
Give you a demo
from .utils import get_settings
from constance import config
from rest_framework.viewsets import ViewSet
class SettingViewSet(ViewSet):
permission_classes = (IsAuthenticated,)
def setting(self, request, allow_settings):
if request.method == 'GET':
# list all setting items
return Response(data=get_settings(allow_settings))
else:
# change all allow setting items in allow_settings
for key in request.data:
if key in allow_settings and key in getattr(settings, 'CONSTANCE_CONFIG', {}):
value = request.data[key]
setattr(config, key, '' if value is None else value)
return Response(data=get_settings(allow_settings))
def create(self, request):
"""
<p>update with POST:<code>{'Key': new_value}</code>
"""
allow_settings = [key for key, options in getattr(settings, 'CONSTANCE_CONFIG', {}).items()]
return self.setting(request, allow_settings)
def list(self, request):
"""
get all setting item
"""
allow_settings = [key for key, options in getattr(settings, 'CONSTANCE_CONFIG', {}).items()]
return self.setting(request, allow_settings)
in utils.py:
def get_settings(allow_settings):
setting_list = []
for key, options in getattr(settings, 'CONSTANCE_CONFIG', {}).items():
if key in allow_settings:
default, help_text = options[0], options[1]
data = {'key': key,
'default': default,
'help_text': help_text,
'value': getattr(config, key)}
setting_list.append(data)
return setting_list
in urls.py:
router.register(r'setting', SettingViewSet, base_name='setting')
ask get http://x.x.x.x/setting/ you can get:
[
{
"key": "staff_prefix",
"default": "",
"help_text": "工号固定代码",
"value": ""
},
{
"key": "staff_suffix",
"default": 3,
"help_text": "工号尾缀",
"value": "3"
}
]
post http://x.x.x.x/setting/ with data {'staff_prefix': 'demo'} you can update value in staff_prefix.
Your serializer needs to know what to return. So for each field, you can do something like this:
class ConstanceSettingSerializer(serializers.Serializer):
setting_name = serializers.SerializerMethodField()
def get_setting_name(self):
settings = constance_settings.CONFIG.items()
# Retrieve your setting's value here and return
return setting_value
That's on the way out. If you wanted to be able to change settings via PUT/POST/PATCH, you need to override create/update in your serializer and implement the logic to save the values yourself.
Another way could be to check how the storage of the settings is implemented.
I have not used the constance settings so i cant say. But if it uses DB to store the settings, you can implement model serializer for the model which they use.
You can also do the same with a serializer if you using database backend:
serializer.py:
from constance.backends.database.models import Constance
from constance import config
from django.conf import settings
from rest_framework import serializers
class ConfigSerializer(serializers.ModelSerializer):
default = serializers.SerializerMethodField()
help_text = serializers.SerializerMethodField()
value = serializers.SerializerMethodField()
class Meta:
model = Constance
fields = ('key', 'default', 'help_text', 'value')
def get_default(self, obj):
default, _ = settings.CONSTANCE_CONFIG.get(obj.key)
return default
def get_help_text(self, obj):
_, help_text = settings.CONSTANCE_CONFIG.get(obj.key)
return help_text
def get_value(self, obj):
return getattr(config, obj.key, None)
views.py:
from rest_framework.viewsets import ReadOnlyModelViewSet
from rest_framework.permissions import IsAuthenticated
from constance.backends.database.models import Constance
from myproject.apps.common.api.v1.serializers import ConfigSerializer
class ConfigAPIView(ReadOnlyModelViewSet):
serializer_class = ConfigSerializer
permission_classes = [IsAuthenticated]
queryset = Constance
urls.py:
from django.urls import path
from myproject.apps.common.api.v1.views import ConfigAPIView
urlpatterns = [
path(
"config/",
ConfigAPIView.as_view({"get": "list"}),
name="config-list",
),
]

Get max value from django rest

I am using django rest framework and instead of getting the complete list of an object, I only want to get a specific value, like max(date) for example. Here is the code I am using:
My Serializer
class MoodSerializer(serializers.ModelSerializer):
class Meta:
model = Mood
fields = ('date', 'rating')
def create(self, validated_data):
return Mood.objects.create(**validated_data)
My View
class MoodList(generics.ListCreateAPIView):
queryset = Mood.objects.all()
serializer_class = MoodSerializer
class MoodDetail(generics.RetrieveUpdateDestroyAPIView):
queryset = Mood.objects.all()
serializer_class = MoodSerializer
My URLS
url(r'^mood/$', views.MoodList.as_view()),
url(r'^mood/(?P<pk>[0-9]+)/$', views.MoodDetail.as_view()),
So if fire a GET on "max_mood" I want the latest Mood entry from the db.
Instead of Mood.objects.all(), why not sort Mood objects by some_criteria and get the first one (which will be the max one). Like this:
mood = Mood.objects.order_by('-some_criteria').first()
There is another way but it requires additional queries:
from django.db.models import Max
maximum = Mood.objects.aggregate(m=Max('some_criteria')).get('m')
max_mood = Mood.objects.get(some_criteria=maximum)
view
class MaxMoodView(generics.RetrieveAPIView):
queryset = Mood.objects.all()
serializer_class = MoodSerializer
def retrieve(self, request, *args, **kwargs):
mood=self.get_queryset().order_by('-date').first()
return Response(self.get_serializer(instance=mood))
urls
url(r'^mood/max_mood/$', views.MaxMoodView.as_view()),
Alright I got it working with the help of the comments:
view:
class MaxMoodView(generics.ListCreateAPIView):
queryset = Mood.objects.all()
serializer_class = MoodSerializer
def get(self, request, format=None):
mood=self.get_queryset().order_by('-date').first()
serializer = MoodSerializer(mood)
return Response(serializer.data)
urls:
url(r'^mood/max_mood/$', views.MaxMoodView.as_view()),

Multiple lookup_fields for django rest framework

I have multiple API which historically work using id as the lookup field:
/api/organization/10
I have a frontend consuming those api.
I'm building a new interface and for some reasons, I would like to use a slug instead an id:
/api/organization/my-orga
The API is built with Django Rest Framework. Except the change of lookup field, the api behavior should stay the same.
Is there a solution to allow my API to work with both a slug and a pk ? Those two path should give them same results:
/api/organization/10
/api/organization/my-orga
Here is my API definition:
# urls.py
router = DefaultRouter()
router.register(r'organization', Organization)
urlpatterns = router.urls
#view.py
class Organization(viewsets.ModelViewSet):
queryset = OrganisationGroup.objects.all()
serializer_class = OrganizationSerializer
# serializer.py
class OrganizationSerializer(PermissionsSerializer):
class Meta:
model = Organization
Try this
from django.db.models import Q
import operator
from functools import reduce
from django.shortcuts import get_object_or_404
class MultipleFieldLookupMixin(object):
def get_object(self):
queryset = self.get_queryset() # Get the base queryset
queryset = self.filter_queryset(queryset) # Apply any filter backends
filter = {}
for field in self.lookup_fields:
filter[field] = self.kwargs[field]
q = reduce(operator.or_, (Q(x) for x in filter.items()))
return get_object_or_404(queryset, q)
Then in View
class Organization(MultipleFieldLookupMixin, viewsets.ModelViewSet):
queryset = OrganisationGroup.objects.all()
serializer_class = OrganizationSerializer
lookup_fields = ('pk', 'another field')
I solved the similar problem by overriding retrieve method and check pk field's value against any pattern. For example if it consists of only numbers.
def retrieve(self, request, *args, **kwargs):
if kwargs['pk'].isdigit():
return super(Organization, self).retrieve(request, *args, **kwargs)
else:
# get and return object however you want here.
I know you asked this question quite a time ago, but here is the complete solution i got from all answers, considering both views and urls:
Put this in your views.py: (With a little edit from drf)
class MultipleFieldLookupMixin(object):
def get_object(self):
queryset = self.get_queryset()
queryset = self.filter_queryset(queryset)
filter = {}
for field in self.lookup_fields:
if self.kwargs.get(field, None):
filter[field] = self.kwargs[field]
obj = get_object_or_404(queryset, **filter) # Lookup the object
self.check_object_permissions(self.request, obj)
return obj
Then inherit your view from this Mixin and add fields you want to lookup_fields. Like this:
class YourDetailView(MultipleFieldLookupMixin, RetrieveUpdateAPIView):
...
lookup_fields = ['pk', 'slug','code']
And in urls.py:
re_path(r'^organization/(?P<pk>[0-9]+)/$',
YourDetailView),
re_path(r'^organization/(?P<slug>[-a-zA-Z0-9_]+)/$',
YourDetailView),
re_path(r'^organization/sth_else/(?P<code>[0-9]+)/$',
YourDetailView),
class MultipleFieldLookupMixin(object):
"""
Apply this mixin to any view or viewset to get multiple field filtering
based on a `lookup_fields` attribute, instead of the default single field filtering.
"""
def get_object(self):
queryset = self.get_queryset() # Get the base queryset
queryset = self.filter_queryset(queryset)
filter = {}
for field in self.lookup_fields:
if self.kwargs[field]: # Ignore empty fields.
filter[field] = self.kwargs[field]
return get_object_or_404(queryset, **filter) # Lookup the object
class RetrieveUserView(MultipleFieldLookupMixin, generics.RetrieveAPIView):
queryset = User.objects.all()
serializer_class = UserSerializer
lookup_fields = ('account', 'username')
I think best way is to override the get_object(self) method
class Organization(generics.RetrieveAPIView):
serializer_class = OrganizationSerializer
queryset = Organization.objects.all()
multiple_lookup_fields = ['pk', 'slug']
def get_object(self):
queryset = self.get_queryset()
filter = {}
for field in self.multiple_lookup_fields:
filter[field] = self.kwargs[field]
obj = get_object_or_404(queryset, **filter)
self.check_object_permissions(self.request, obj)
return obj
There are a lot of answers here already, but none provide a full description including the mixin, view, and url configuration. This answer does.
This is the mixin that works best, it is slightly modified from https://www.django-rest-framework.org/api-guide/generic-views/#creating-custom-mixins to not error out on non-existing fields.
class MultipleFieldLookupMixin:
"""
Apply this mixin to any view or viewset to get multiple field filtering
based on a `lookup_fields` attribute, instead of the default single field filtering.
Source: https://www.django-rest-framework.org/api-guide/generic-views/#creating-custom-mixins
Modified to not error out for not providing all fields in the url.
"""
def get_object(self):
queryset = self.get_queryset() # Get the base queryset
queryset = self.filter_queryset(queryset) # Apply any filter backends
filter = {}
for field in self.lookup_fields:
if self.kwargs.get(field): # Ignore empty fields.
filter[field] = self.kwargs[field]
obj = get_object_or_404(queryset, **filter) # Lookup the object
self.check_object_permissions(self.request, obj)
return obj
Now add the view as follows, it is important to have the Mixin first, otherwise the get_object method is not overwritten:
class RudAPIView(MultipleFieldLookupMixin, generics.RetrieveUpdateDestroyAPIView):
...
lookup_fields = ['pk', 'other_field']
Now, for the urls, we use default converters. It is important int comes first as that one will actually check if it is an int, and if not fallback to str. If you have more complex fields, you need to resort to regex.
path('efficiency/<int:pk>/', views.RudAPIView.as_view(), name='something-rud'),
path('efficiency/<string:other_field>/', views.RudAPIView.as_view(), name='something-rud'),
I think the fundamental answer is that this would not be good REST/API design and just isn't something DRF would enable.
The official docs have an example for this at https://www.django-rest-framework.org/api-guide/generic-views/#creating-custom-mixins
Also, you need to modify the urls.py adding a new route for the same view, but with the new field name.
If you still would like to use Viewsets without breaking it apart, here you go.
(Test passed on my end)
import operator
from functools import reduce
from django.db.models import Q
from django.shortcuts import get_object_or_404
class MultipleFieldLookupMixin(object):
def get_object(self):
queryset = self.get_queryset() # Get the base queryset
queryset = self.filter_queryset(queryset) # Apply any filter backends
filters = {}
pk_fields = ["pk", "id"]
for field in self.lookup_fields:
identifier = self.kwargs[self.lookup_field]
if (field in pk_fields and identifier.isdigit()) or field not in pk_fields:
filters[field] = self.kwargs[self.lookup_field]
q = reduce(operator.or_, (Q(x) for x in filters.items()))
obj = get_object_or_404(queryset, q)
self.check_object_permissions(self.request, obj)
return obj
This is my latest version that supports primary key fields that not necessary are strings, I think is more resilient.
import operator
from functools import reduce
from django.db.models import Q
from django.shortcuts import get_object_or_404
from django.core.exceptions import ValidationError
class MultipleFieldLookupMixin:
"""
Apply this mixin to any view or viewset to get multiple field filtering
based on a `lookup_fields` attribute, instead of the default single field filtering.
"""
def get_object(self):
queryset = self.get_queryset() # Get the base queryset
queryset = self.filter_queryset(queryset) # Apply any filter backends
filters = {}
for field in self.lookup_fields:
try:
# Validate the data type we got is a valid data type for the field we are setting
self.get_serializer_class().Meta.model._meta.get_field(field).to_python(
self.kwargs[self.lookup_field]
)
filters[field] = self.kwargs[self.lookup_field]
except ValidationError:
continue
query = reduce(operator.or_, (Q(x) for x in filters.items()))
obj = get_object_or_404(queryset, query)
self.check_object_permissions(self.request, obj)
return obj

Categories

Resources