I managed to get data from the tables MemberDeclareRecept and Member with the following config files. Here I am looking for the MemberDeclareRecept.pk. But how can I get all the data if I search the Member.CoId instead?
The MemberSearchByCode view gives all the members in the table but I can't get the specific member.
Here are my models
class Member(models.Model):
Name = models.CharField(max_length=40,null=True)
FirstName = models.CharField(max_length=40,null=True)
DateBirth = models.DateField(,null=True)
CoId = models.CharField(max_length=6,null=True)
class MemberDeclareRecept(models.Model):
SyMember=models.ForeignKey(Member,verbose_name='Name member ',null=True,related_name='Member')
DateDeclareRecept=models.DateField(('Date received',null=True)
And the serializers that are being used
class MemberDeclareSerializer(serializers.ModelSerializer):
member = serializers.StringRelatedField(read_only=True)
SyRecept=serializers.StringRelatedField(read_only=True)
class Meta:
model = MemberDeclareRecept
fields=('id','member','SyRecept')
And the views that I am currently using
class MemberDeclareDetail(generics.ListCreateAPIView):
queryset=MemberDeclareRecep.objects.all()
serializer_class =MemberDeclareSerializer
def get_object(self,pk):
try:
return self.queryset.get(pk=pk)
except MemberDeclareRecep.DoesNotExist:
raise Http404
def get(self, request, pk,format=None):
entries = self.get_object(pk)
serializer = MemberDeclareSerializer(entries)
return Response(serializer.data)
class MemberSearchByCode(generics.ListAPIView):
serializer_class =MemberSerializer
def get_queryset(self):
member=self.request.QUERY_PARAMS.get(Co_id',None)
if membre is not None:
queryset = queryset.filter(member__Name=member
return queryset
It appears as though you've found an answer, based on the comment, and it's included below.
class MemberSearch(generics.ListAPIView):
serializer_class=MemberDeclareSerializer
def get_queryset(self):
member = self.kwargs['Co_id']
return member_declare_recept.objects.filter(member__Co_id=member)
It is important to note that this is not filtering the queryset based on query parameters, this is filtering it based on parameters present in the url. If you were filtering it based on query parameters, which is useful if you will need to get a list of all objects at once, the url that you would be using would be like
/api/members/?company=[co_id]
Which would make the company id optional. In your case though, the id is being embedded within the url itself. This is typically referred to as hierarchal urls, and it's generally not recommended, but your urls end up like
/api/company/[co_id]/members
Which is preferable for some, and even required in certain cases.
Now, if you wanted to use the query parameter instead of the url parameter for filtering, only a slight change would be required in your code.
class MemberSearch(generics.ListAPIView):
serializer_class=MemberDeclareSerializer
def get_queryset(self):
company_id = self.request.query_parameters.get('company', None)
if not company_id:
return member_declare_recept.objects.all()
return member_declare_recept.objects.filter(member__Co_id=company_id)
This has the additional advantage of also being support directly through django-filter and the DjangoFilterBackend.
Related
I'm working on my Django SAAS app in which I want to allow the user to have some custom settings, like disable or enable certain filters. For that I'm using django-user-setttings combined with django-filters and simple forms with boolean fields:
class PropertyFilterSetting(forms.Form):
filter_by_loans = forms.BooleanField(required=False)
filter_by_tenants = forms.BooleanField(required=False)
The issue is that when trying to apply those settings, I keep running into serious spaghetti code:
views.py
class PropertyListView(LoginRequiredMixin, FilterView):
template_name = 'app/property_list.html'
context_object_name = 'properties'
def get_filterset_class(self):
print(get_user_setting('filter_by_tenants', request=self.request))
return PropertyFilterWithoutTenant if not get_user_setting('filter_by_tenants', request=self.request)['value'] else PropertyFilter
filter.py
class PropertyFilter(django_filter.FilterSet):
...
class PropertyFilterWithoutTenant(PropertyFilter):
...
and I'd have to do the same thing with the rest of the features. Is there any better way to implement this?
You can create methods in your User model, or a new class which acts as a store for all the methods. Each method will give you the relevant filterset class based on the value of corresponding user setting.
Something like:
class UserFilterset:
def __init__(self, request):
self.request = request
def get_property_filterset(self):
if not get_user_setting('filter_by_tenants', request=self.request)['value']:
return PropertyFilterWithoutTenant
return PropertyFilter
... # add more such methods for each user setting
Now you can use this method to get the relevant filterset class
class PropertyListView(LoginRequiredMixin, FilterView):
template_name = 'app/property_list.html'
context_object_name = 'properties'
def get_filterset_class(self):
return UserFilterset(self.request).get_property_filterset()
So even if in future you want to add some more logic, you can just update the relevant method, it would be cleaner and manageable.
I'm not sure how MVT stucture will exactly respond to this one but i use a custom generic class in REST structure to add custom filter fields in any viewset that i want
class ListAPIViewWithFilter(ListAPIView):
def get_kwargs_for_filtering(self):
filtering_kwargs = {}
if self.my_filter_fields is not []:
# iterate over the filter fields
for field in self.my_filter_fields:
# get the value of a field from request query parameter
field_value = self.request.query_params.get(field)
if field_value:
filtering_kwargs[field] = field_value
return filtering_kwargs
def get_queryset(self):
queryset = super(ListAPIViewWithFilter, self).get_queryset()
filtering_kwargs = self.get_kwargs_for_filtering()
if filtering_kwargs != {}:
# filter the queryset based on 'filtering_kwargs'
queryset = queryset.filter(**filtering_kwargs)
self.pagination_class = None
else:
return queryset
return queryset[:self.filter_results_number_limit]
changing origional get_queryset function in views.py should be the key to solving your problem (it works in django rest).
try checking what function gets the data then just identify the field wanted from it.
What i currently have setted up with django rest is :
MODEL NAME : Animes
/api/ <- root
/api/animes/ <- lists all animes available in my db
/api/animes/id/ <- returns the anime instance that has id=id
MODEL NAME : Episodes
/api/ <- root
/api/episodes/ <- lists all episodes of all animes available in my db
/api/episodes/id/ <- returns the episode instance that has id=id
So basically im trying to achieve is :
if i request api/episodes/{anime_name}/
i get that specific anime's Episodes listed .
how can i do that ?
EpisodesSerializer
class EpisodesSerializer(serializers.ModelSerializer):
class Meta:
model = Episodes
fields = '__all__'
Router
router = routers.DefaultRouter()
router.register('animes', AnimesViewSet, 'animes')
router.register('episodes', EpisodesViewSet, 'episodes')
urlpatterns = router.urls
EpisodesViewSet
class EpisodesViewSet(viewsets.ModelViewSet):
permission_classes = [permissions.AllowAny]
MainModel = Episodes
queryset = Episodes.objects.all()
serializer_class = EpisodesSerializer
EDIT
As the OP mentioned in the comments, the newer versions of DRF use #action instead as #detail_route and #list_route are deprecated.
To use a different field for lookup, you can implement the logic to get the object yourself but you have to make sure that the field you're using for lookup is unique, else you can have many objects returned.
So assuming the anime name is unique and you want to use it for lookup, you can do this:
#action(detail=True, methods=['get'])
def episodes(self, *args, **kwargs):
anime = Anime.objects.get(name=self.kwarg[self.lookup_field])
episodes = Episode.objects.filter(anime=anime)
...
You can also check how get_object() is implemented to make it more robust.
I made a generic view mixin for myself that allows lookup with multiple unique fields aprt from the main pk lookup field:
class AlternateLookupFieldsMixin(object):
"""
Looks up objects for detail endpoints using alternate
lookup fields assigned in `alternate_lookup_fields` apart
from the default lookup_field. Only unique fields should be used
else Http404 is raised if multiple objects are found
"""
alternate_lookup_fields = []
def get_object(self):
try:
return super().get_object()
except Http404:
lookup_url_kwarg = self.lookup_url_kwarg or self.lookup_field
queryset = self.filter_queryset(self.get_queryset())
obj = None
for lookup_field in self.alternate_lookup_fields:
filter_kwargs = {lookup_field: self.kwargs[lookup_url_kwarg]}
try:
obj = get_object_or_404(queryset, **filter_kwargs)
except Http404:
pass
if obj:
self.check_object_permissions(self.request, obj)
return obj
raise Http404
All you have to do is add it to your view's base classes and add the fields for lookup(name in your case) in the alternate_lookup_fields attribute. Of course, only use unique fields.
As for filtering, you can check how simple filtering is done here.
However, I will recommend using a more generic filter backend like django-filter
ORIGINAL ANSWER
First of all, the url will look more initutive like this:
api/anime/<anime_id>/episodes/
This is because you should usually start from a more generic resource to more specific ones.
To achieve this, in your AnimeViewSet(not EpisodesViewSet), you can have a detail route for the episodes like this:
from rest_framework.decorators import detail_route
#detail_route(methods=['get'])
def episodes(self, *args, **kwargs):
anime = self.get_object()
episodes = Episode.objects.filter(anime=anime)
page = self.paginate_queryset(anime)
if page is not None:
serialier = EpisodesSerializer(page, context=self.get_serializer_context(), many=True)
return self.get_paginated_response(serializer.data)
serializer = EpisodesSerializer(episodes, context=self.get_serializer_context()) many=True)
return Response(serializer.data)
You could also just use a filter on the EpisodesViewSet to fetch episodes belonging to a particular anime this way:
api/episodes?anime=<anime_id>
I'm in a situation where I want to change the serializer field depending on a condition. Where the condition comes doesn't matter but I want to be able to switch between serializer fields as the following example:
class EntrySerializer(serializers.ModelSerializer):
# author defined from ModelSerializer
def get_author_serializer(self):
request = self.context.get('request')
GET = getattr(request, 'GET', {})
if request and GET and GET.get('include_author')=='true':
author_serializer = UserSerializer()
else:
author_serializer = serializers.PrimaryKeyRelatedField(
read_only=True, default=serializers.CurrentUserDefault()
)
return author_serialize
Of course this doesn't work because get_FIELD_serializer doesn't exist, but I'm looking for the simplest solution that can do this.
I've tried writing author as a property in a naive attempt but it didn't work.
I am aware that I can write multiple EntrySerializers and use get_serializer_class but that is just too much boilerplate code for such a small customization.
If you just want to change a serializers' field based on a condition, you could do something like this.
class MySerializer(serializers.ModelSerializer):
author = serializers.SerializerMethodField()
def get_author(self, instance):
# your condition here
if your_condition:
return instance.author
return 'hello'
Check the docs for SerializerMethodField
https://www.django-rest-framework.org/api-guide/fields/#serializermethodfield
I'm working on a generic ListView for a Family object. Each Family has a one-to-one field with an employee_user and a partner_user, which in turn have one-to-one fields with a Profile model (with a related name user_profile) which has a Boolean using_app field. A family is considered to be using the app if either the employee or the partner is using the app.
Currently, I have the following get_queryset() method
def get_queryset(self):
queryset = super().get_queryset()
if app:
using_app = app == 't'
if using_app:
queryset = queryset.filter(
Q(employee_user__user_profile__using_app=using_app) |
Q(partner_user__user_profile__using_app=using_app))
else:
queryset = queryset.filter(
Q(employee_user__user_profile__using_app=using_app) &
Q(partner_user__user_profile__using_app=using_app))
return queryset
This makes this ListView pass the following tests (using factory_boy test fixtures):
from django.test import TestCase
from django.urls import reverse
from dashboard.tests.utils import AccountMixin
from lucy_web.test_factories import FamilyFactory, UserFactory
class FamilyUsingAppTest(AccountMixin, TestCase):
def setUp(self):
self.login_user(UserFactory(is_superuser=True))
def test_filter_families_using_app(self):
family = FamilyFactory(
employee_user=UserFactory(user_profile__using_app=True),
partner_user=UserFactory(user_profile__using_app=False))
self.assertIs(family.using_app, True)
response = self.client.get(
path=reverse('dashboard:families'),
data={'app': 't'})
self.assertEqual(response.context['object_list'].count(), 1)
self.assertTrue(response.context['object_list'].first() == family)
def test_filter_families_not_using_app(self):
family = FamilyFactory(
employee_user=UserFactory(user_profile__using_app=True),
partner_user=UserFactory(user_profile__using_app=False))
response = self.client.get(
path=reverse('dashboard:families'),
data={'app': 'f'})
self.assertEqual(response.context['object_list'].count(), 0)
However, the get_queryset method contains some code repetition in the if... else clauses, and I'm looking to refactor it, for example by using Queryset.annotate(). From Django Boolean Queryset Filter Not Working, however, I wasn't able to find a concise way to do this. Any suggestions on how to refactor this code while still passing these tests?
I am fairly new to Django rest framework and I had a couple questions that would really clear up a lot of stuff for me.
I was looking at docs for simple CRUD generic views like ListAPIView, Retrieve... etc.
For my list view I created it like this:
class CourseListApiView(ListAPIView):
queryset = Course.objects.all()
serializer_class = CourseListSerializer
Which makes sense because of the queryset returns Course.objects.all() so all the courses appear.
What I am not clear about is how the queryset in RetrieveApi works
class CourseRetrieveAPIView(RetrieveAPIView):
queryset = Course.objects.all()
serializer_class = CourseRetrieveSerializer
This is my retrieve view, it takes pk from my link and returns a corresponding course. What is unclear to me is why the queryset is Course.objects.all(), not a filtered query that gets the kwargs from the URL and filters my Courses. I tried it my way and got the same results, my view was:
class CourseRetrieveAPIView(RetrieveAPIView):
serializer_class = CourseRetrieveSerializer
def get_queryset(self):
queryset = Course.objects.filter(pk=self.kwargs.get('pk'))
return queryset
This makes more sense since the queryset is Course.objects.filter(pk=self.kwargs.get('pk')) instead of Course.objects.all() which to me doesn't make sense since I am filtering my courses by the pk in the URL
Hope my question made sense. Leave a comment if you need any clarification. I know the answer will be pretty obvious but I am very new to the framework
You will have to go through the codebase of rest_framework. A function named get_object uses two class variables named lookup_field and lookup_url_kwarg which have a default value of pk and None respectively.
Excerpt from the GenericAPIView in rest_framework/generics.py
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
As you can see the lookup_url_kwarg is set to be equal to lookup_field if nothing is specified. If you change this value to a field of your requirement then the filter in get_object_or_404 changes.
Now coming back to your issue, when you are specifying the filter manually with url kwargs you are not using the functionality provided by the RetrieveAPIView. Instead what you are doing is filtering out your result with pk from url kwargs in get_queryset and then sending that QuerySet result to get_object which will again do the same thing for you.