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?
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.
I created a Django REST API using serializers, viewsets and routers. My end points looks something like this:
http://www.website.com/api/items
http://www.website.com/api/items/available
serializer.py (omitting imports)
class ItemSerializer(serializers.ModelSerializer):
class Meta:
model = Item
fields = '__all__'
viewsets.py (omitting imports)
class ItemViewSet(viewsets.ModelViewSet):
queryset = Item.objects.all()
serializer_class = ItemSerializer
#action(methods=['GET'], detail=False)
def most_expensive(self, request):
query = self.get_queryset().order_by('price').last()
serialized = self.serializer_class(query)
return Response(serialized.data)
Now I want to able to access this API from my views.py to render the HTML with the available items:
This is the way im doing it right now:
views.py (omitting imports)
class ProductListView(View):
template = 'store/product_list.html'
def get(self, request):
items = requests.get('http://127.0.0.1:8000/api/items/available')
context = {'items': items}
return render(request, self.template, context=context)
Using the requests modules I have a couple of concerns, after measuring I noticed there is a 0.015 second delay for that request to go through and if I ever change the API endpoint I would have to adjust it here since its hard coded.
I can get my items using:
Item.objects.filter(available=True)
Which gives me the result pretty much instantly but I'm writing all the queries twice (once in my API and once in my views.py)
Is there a better way of doing this like calling the viewset class directly and getting the data from there?
Many thanks!
Calling the API endpoint in the same app is not considered a good practise.
An option would be to call your viewset method directly, like in https://stackoverflow.com/a/51149806/290036
The other one that I recommend is to use the same codebase for your API and for the view.
def get_avaialble_items():
items = Item.objects.filter(available=True)
...
return items
# Use get_avaialble_items both in ItemViewSet and ProductListView
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 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.
Basically what I'm trying to achieve is a multi-model django app where different models take advantage of the same views. For example I've got the models 'Car' 'Make' 'Model' etc and I want to build a single view to perform the same task for each, such as add, delete and edit, so I don't have to create a seperate view for add car, ass make etc. I've built a ModelForm and Model object for each and would want to create a blank object when adding and a pre-populated form object when editing (through the form instance arg), with objects being determined via url parameters.
Where I'm stuck is that I'm not sure what the best way to so this is. At the moment I'm using a load of if statements to return the desired object or form based on parameters I'm giving it, which get's a bit tricky when different forms need specifying and whether they need an instance or not. Although this seems to be far from the most efficient way of achieving this.
Django seems to have functions to cover most repetitive tasks, is there some magic I'm missing here?
edit - Here's an example of what I'm doing with the arguments I'm passing into the url:
def edit_object(request, object, id):
if(object==car):
form = carForm(instance = Car.objects.get(pk=id)
return render(request, 'template.html', {'form':form})
What about using Class Based Views? Using CBVs is the best way in Django to make reusable code. For this example maybe it can be a little longer than function based views, but when the project grows up it makes the difference. Also remember "Explicit is better than implicit".
urls.py
# Edit
url(r'^car/edit/(?P<pk>\d+)/$', EditCar.as_view(), name='edit-car'),
url(r'^make/edit/(?P<pk>\d+)/$', EditMake.as_view(), name='edit-make'),
# Delete
url(r'^car/delete/(?P<pk>\d+)/$', DeleteCar.as_view(), name='delete-car'),
url(r'^make/delete/(?P<pk>\d+)/$', DeleteMake.as_view(), name='delete-make'),
views.py
class EditSomethingMixin(object):
"""Use Mixins to reuse common behavior"""
template_name = 'template-edit.html'
class EditCar(EditSomethingMixin, UpdateView):
model = Car
form_class = CarForm
class EditMake(EditSomethingMixin, UpdateView):
model = Make
form_class = MakeForm
class DeleteSomethingMixin(object):
"""Use Mixins to reuse common behavior"""
template_name = 'template-delete.html'
class DeleteCar(DeleteSomethingMixin, DeleteView):
model = Car
class DeleteMake(DeleteSomethingMixin, DeleteView):
model = Make
Just pass your class and form as args to the method then call them in the code.
def edit_object(request, model_cls, model_form, id):
form = model_form(instance = model_cls.objects.get(pk=id)
return render(request, 'template.html', {'form':form})
then just pass in the correct classes and forms in your view methods
def edit_car(request,id):
return edit_object(request, Car, CarForm, id)
each method knows what classes to pass, so you eliminate the if statements.
urls.py
url(r'^car/delete/(?<pk>\d+)/', edit, {'model': Car})
url(r'^make/delete/(?<pk>\d+)/', edit, {'model': Make})
views.py
def edit(request, id, model):
model.objects.get(id=id).delete()