Django REST, Accessing methods of the selected object - django

this part of my code fills the queryset with [category_object].subcats.all(). let subcats be a method of category object:
serializer:
class CatSrlz(serializers.ModelSerializer):
class Meta:
model = Category
fields = ('id', 'label', )
View:
class CatsViewSet(viewsets.ReadOnlyModelViewSet):
queryset = Category.objects.filter(parent=None)
serializer_class = CatSrlz
def retrieve(self, request, *args, **kwargs):
# return Response({'res': self.kwargs})
queryset = Category.objects.get(pk=str(self.kwargs['pk'])).subCats.all()
dt = CatSrlz(queryset, many=True)
return Response(dt.data)
and url:
router.register(r'cats', views.CatsViewSet)
it works but i'm pretty sure that there must be a more correct way of doing so
Is there one?
thanks

When retrieving a single object, you can use the get_object method in your view, which look like this in DRF without modifications :
def get_object(self):
"""
Returns the object the view is displaying.
You may want to override this if you need to provide non-standard
queryset lookups. Eg if objects are referenced using multiple
keyword arguments in the url conf.
"""
queryset = self.filter_queryset(self.get_queryset())
# Perform the lookup filtering.
lookup_url_kwarg = self.lookup_url_kwarg or self.lookup_field
assert lookup_url_kwarg in self.kwargs, (
'Expected view %s to be called with a URL keyword argument '
'named "%s". Fix your URL conf, or set the `.lookup_field` '
'attribute on the view correctly.' %
(self.__class__.__name__, lookup_url_kwarg)
)
filter_kwargs = {self.lookup_field: self.kwargs[lookup_url_kwarg]}
obj = get_object_or_404(queryset, **filter_kwargs)
# May raise a permission denied
self.check_object_permissions(self.request, obj)
return obj
So you could adapt the part where you get your object :
filter_kwargs = {self.lookup_field: self.kwargs[lookup_url_kwarg]}
obj = get_object_or_404(queryset, **filter_kwargs)
and add your subcat logic there.
By the way, I don't get why you are using
dt = CatSrlz(queryset, many=True)
Shouldn't "retrieve" return a single object?

Related

DoesNotExist - matching query does not exist

I have this django class-based view where i am trying to overwrite the get_queryset function because i want get to the inserted values from the frontend to search in my database after the subject with that name and then get the id. but when i call the view it gives me a "Subject matching query does not exist." because the subject_val is None. That makes sense because the user has not submitted the values jet.. so how do i get it to wait until a user have choosen "submit
class AttendanceList(LoginRequiredMixin, ListView):
model = AttendanceLog
template_name = "./attendancecode/showattendance.html"
def get_queryset(self):
class_val = self.request.GET.get('class')
subject_val = self.request.GET.get('subject')
sub = Subject.objects.get(name=subject_val).id
new_context = get_statstic(class_val, sub)
return new_context
def get_context_data(self, **kwargs):
context = super(AttendanceList, self).get_context_data(**kwargs)
context['class'] = self.request.GET.get('class')
context['subject'] = self.request.GET.get('subject')
return context
You can check if the values are not None, in case they are, you need to return another queryset of AttendanceLogs (for example AttendanceLog.objects.all() or AttendanceLog.objects.none()):
class AttendanceList(LoginRequiredMixin, ListView):
model = AttendanceLog
template_name = "./attendancecode/showattendance.html"
def get_queryset(self):
class_val = self.request.GET.get('class')
subject_val = self.request.GET.get('subject')
if class_val is not None and subject_val is not None:
sub = Subject.objects.get(name=subject_val).id
return get_statstic(class_val, sub)
# return another queryset:
return AttendanceLog.objects.none()
# …

How to share validation and schemas between a DRF FilterBackend and a Serializer

I am implementing some APIs using Django Rest Framework, and using the generateschema command to generate the OpenApi 3.0 specs afterwards.
While working on getting the schema to generate correctly, I realized that my code seemed to be duplicating a fair bit of logic between the FilterBackend and Serializer I was using. Both of them were accessing and validating the query parameters from the request.
I like the way of specifying the fields in the Serializer (NotesViewSetGetRequestSerializer in my case), and I would like to use that in my FilterBackend (NoteFilterBackend in my case). It would be nice to have access to the validated_data within the filter, and also be able to use the serializer to implement the filtering schemas.
Are there good solutions out there for only needing to specify your request query params once, and re-using with the filter and serializer?
I've reproduced a simplified version of my code below. I'm happy to provide more info on ResourceURNRelatedField if it's needed (it extends RelatedField and uses URNs instead of primary keys), but I think this would apply to any kind of field.
class NotesViewSet(generics.ListCreateAPIView, mixins.UpdateModelMixin):
allowed_methods = ("GET")
queryset = Note.objects.all()
filter_backends = [NoteFilterBackend]
serializer_class = NotesViewSetResponseSerializer
def get(self, request, *args, **kwargs):
query_params_dict = request.query_params
request_serializer = NotesViewSetGetRequestSerializer(data=query_params_dict)
request_serializer.is_valid(raise_exception=True)
validated_data = request_serializer.validated_data
member = validated_data.get("member_urn")
team = validated_data.get("team_urn")
if not provider_can_view_member(request.user, member, team):
return custom404(
request,
HttpResponseNotFound(
"Member does not exist!. URN={}".format(member.urn())
),
)
return super(NotesViewSet, self).list(request, *args, **kwargs)
class NotesViewSetGetRequestSerializer(serializers.Serializer):
member_urn = ResourceURNRelatedField(queryset=User.objects.all(), required=True)
team_urn = ResourceURNRelatedField(queryset=Team.objects.all(), required=True)
privacy_scope = serializers.CharField(required=False)
def validate_privacy_scope(self, value):
choices = dict(Note.PRIVACY_SCOPE_CHOICES)
if value and value not in choices:
raise serializers.ValidationError(
"bad privacy scope {}. Supported values are: {}".format(value, choices)
)
else:
return value
class NoteFilterBackend(BaseFilterBackend):
def filter_queryset(self, request, queryset, view):
member_urn = request.query_params.get("member_urn")
customer_uuid = URN.from_urn(member_urn).id
privacy_scope = request.query_params.get("privacy_scope")
team_urn = request.query_params.get("team_urn")
team_uuid = URN.from_urn(team_urn).id
queryset = queryset.filter(customer__uuid=customer_uuid)
if privacy_scope == Note.PRIVACY_SCOPE_TEAM_PROVIDERS:
queryset = queryset.filter(team__uuid=team_uuid)
return queryset

HttpResponse error django generic template

I am able to render class based view generic ListView template using parameter hard coded in views.py.
class ResourceSearchView(generic.ListView):
model = creations
context_object_name = 'reviews'
template_name = 'reviews.html'
query = 'theory'
# def get(self, request):
# if request.GET.get('q'):
# query = request.GET.get('q')
# print(query)
queryset = creations.objects.filter(narrative__contains=query).order_by('-post_date')
However, when parameter is sent via form by GET method (below),
class ResourceSearchView(generic.ListView):
model = creations
context_object_name = 'reviews'
template_name = 'reviews.html'
query = 'theory'
def get(self, request):
if request.GET.get('q'):
query = request.GET.get('q')
print(query)
queryset = creations.objects.filter(narrative__contains=query).order_by('-post_date')
I receive this error
The view creations.views.ResourceSearchView didn't return an
HttpResponse object. It returned None instead.
Note that the parameter name q and associated value is being retrieved successfully (confirmed using print(query)).
So with CBV in Django, you have to return some kind of valid response that the interpreter can use to perform an actual HTTP action. Your GET method isn't returning anything and that's what is making Django angry. You can render a template or redirect the user to a view that renders a template but you must do something. One common pattern in CBV is to do something like:
return super().get(request, *args, **kwargs)
...which continues up the chain of method calls that ultimately renders a template or otherwise processes the response. You could also call render_to_response() directly yourself or if you're moving on from that view, redirect the user to get_success_url or similar.
Have a look here (http://ccbv.co.uk) for an easy-to-read layout of all the current Django CBVs and which methods / variables they support.
Thanks for the responses. Here is one solution.
class ResourceSearchView(generic.ListView):
model = creations
context_object_name = 'reviews'
template_name = 'reviews.html'
def get_queryset(self):
query = self.request.GET.get('q')
queryset = creations.objects.filter(narrative__contains=query).order_by('-post_date')
return queryset

Django Rest Framework return empty list in results randomly, but count still correct

When I'm trying to use custom filter it's working all the time in local development, but when we deploy to Production (Docker Swarm) We found an issue that sometime the API response is return empty results randomly, but the count is correct. Below is the example results from the API
API Response
{
'count': 1,
'next': 'http://localhost:8000/api/somethings/?email=test%40example.com&limit=0&offset=0',
'previous': None,
'results': []
}
Right now we need to restart a uwsgi service (By restarting docker swarm for this service) and the problem is fixed for a moment and randomly happen again.
Here is our DRF view
class SomeView(ListCreateAPIView):
queryset = SomeModel.objects.all()
serializer_class = SomeModelSerializer
filter_backends = (OrderingFilter, DjangoFilterBackend)
filter_class = CustomFilter
ordering = ('id',)
def list(self, request, *args, **kwargs):
if request.GET.get('all', None):
# Do something
serializer = self.get_serializer(queryset, many=True)
return Response(serializer.data)
else:
return super(SomeView, self).list(self, request, *args, **kwargs)
Here is our CustomFilter
from django_filters.rest_framework.filters import CharFilter
import rest_framework_filters as filters
class CustomFilter(filters.FilterSet):
json_field_separator = '___'
json_field_is = CharFilter(name='json_field', method='filter_json_field')
json_field_is_not = CharFilter(name='json_field', method='exclude_json_field')
def split_lookup_field(self, value):
return dict(field.split(':') for field in value.split(self.json_field_separator))
def filter_json_field(self, queryset, name, value):
try:
lookup_field = self.split_lookup_field(value)
return queryset.filter(**lookup_field)
except (ValueError, FieldError):
return queryset.none()
def exclude_json_field(self, queryset, name, value):
try:
lookup_field = self.split_lookup_field(value)
except (ValueError, FieldError):
return queryset.none()
for query_arg, query_value in lookup_field.items():
queryset = queryset.exclude(**{query_arg: query_value})
return queryset
class Meta:
model = SomeModel
exclude = ['image', 'json_field']
Here is a version of Package we use for this project
Django==1.10.8
djangorestframework==3.6.4
django-filter==1.0.4
djangorestframework-filters==0.10.2
In GenericAPIView you can find a method which is called: get_queryset(), which looks like this:
def get_queryset(self):
"""
Get the list of items for this view.
This must be an iterable, and may be a queryset.
Defaults to using `self.queryset`.
This method should always be used rather than accessing `self.queryset`
directly, as `self.queryset` gets evaluated only once, and those results
are cached for all subsequent requests.
You may want to override this if you need to provide different
querysets depending on the incoming request.
(Eg. return a list of items that is specific to the user)
"""
assert self.queryset is not None, (
"'%s' should either include a `queryset` attribute, "
"or override the `get_queryset()` method."
% self.__class__.__name__
)
queryset = self.queryset
if isinstance(queryset, QuerySet):
# Ensure queryset is re-evaluated on each request.
queryset = queryset.all()
return queryset
It is a quick call, but I think your queryset is not re-evaluated on each request.
Take a look at this comment: # Ensure queryset is re-evaluated on each request.

How to upgrade an API from pre 3.0 for Django Rest Framework and getting RetrieveAPIView to work with multiple PKs

I'm upgrading an API from pre 3.0 for Django Rest Framework. It's turning out to be a real struggle for RetrieveAPIView.
Here's my old code:
class ObjectLogLatest(generics.RetrieveAPIView):
serializer_class = ObjectLogSerializer
model = models.ObjectLog
def get_queryset(self):
model_one = self.kwargs['s_pk']
model_two = self.kwargs['p_pk']
user = self.request.user
if self.request.user.is_superuser:
try:
return models.ObjectLog.objects.filter(model_one=model_one).filter(model_two=model_two).latest('created')
except:
pass
else:
try:
return models.SolutionLog.objects.filter(user=self.request.user).filter(model_one=model_one).filter(model_two=model_two).latest('created')
except:
pass
Here's my new code:
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) # Apply any filter backends
filter = {}
for field in self.lookup_fields:
filter[field] = self.kwargs[field]
return get_object_or_404(queryset, **filter) # Lookup the object
class ObjectLogLatest(MultipleFieldLookupMixin, generics.RetrieveAPIView):
serializer_class = ObjectLogSerializer
lookup_fields = ('model_one', 'model_two')
def get_queryset(self):
model_one = models.Model_one.objects.get(id=self.kwargs['s_pk'])
model_two = models.Model_two.objects.get(id=self.kwargs['p_pk'])
if self.request.user.is_superuser:
try:
return models.ObjectLog.objects.filter(model_one=model_one).filter(model_two=model_two).latest('created')
except:
pass
else:
try:
return models.ObjectLog.objects.filter(user=self.request.user).filter(model_one=model_one).filter(model_two=model_two).latest('created')
except:
pass
Here's the URL:
url(r'^api/sll/(?P<p_pk>\d+)/(?P<s_pk>\d+)/$', scen_views.SolutionLogLatest.as_view()),
I'm wondering how to do the same thing as the old code to find the specific ObjectLog while filtering for model_one and model_two.
The mixin comes from this article: http://www.django-rest-framework.org/api-guide/generic-views/
Any thoughts are welcome. Am I even headed in the right direction?