Render fields conditionally with Django-Filters - python

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.

Related

How can i possibly make a dynamic REST API link of a Model that changes by id of another Model?

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>

get_FIELD_serializer in Django Rest Framework

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

Refactoring a Django Boolean query

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?

Django Class Based View UpdateView Restricted User

I am trying to use a Django UpdateView to display an update form for the user. https://docs.djangoproject.com/en/1.8/ref/class-based-views/generic-editing/
I only want the user to be able to edit their own form.
How can I filter or restrict the the objects in the model to only show objects belonging to the authenticated user?
When the user only has one object I can use this:
def get_object(self, queryset=None):
return self.request.user.profile.researcher
However, I now need the user to be able to edit multiple objects.
UPDATE:
class ExperimentList(ListView):
model = Experiment
template_name = 'part_finder/experiment_list.html'
def get_queryset(self):
self.researcher = get_object_or_404(Researcher, id=self.args[0])
return Experiment.objects.filter(researcher=self.researcher)
class ExperimentUpdate(UpdateView):
model = Experiment
template_name = 'part_finder/experiment_update.html'
success_url='/part_finder/'
fields = ['name','short_description','long_description','duration', 'city','address', 'url']
def get_queryset(self):
qs = super(ExperimentUpdate, self).get_queryset()
return qs.filter(researcher=self.request.user.profile.researcher)
URL:
url(r'^experiment/update/(?P<pk>[\w\-]+)/$', login_required(ExperimentUpdate.as_view()), name='update_experiment'),
UpdateView is only for one object; you'd need to implement a ListView that is filtered for objects belonging to that user, and then provide edit links appropriately.
To prevent someone from simply putting the URL for an edit view explicitly, you can override get_object (as you are doing in your question) and return an appropriate response.
I have successfully been able to generate the list view and can get
the update view to work by passing a PK. However, when trying to
override the UpdateView get_object, I'm still running into problems.
Simply override the get_queryset method:
def get_queryset(self):
qs = super(ExperimentUpdate, self).get_queryset()
# replace this with whatever makes sense for your application
return qs.filter(user=self.request.user)
If you do the above, then you don't need to override get_object.
The other (more complicated) option is to use custom form classes in your UpdateView; one for each of the objects - or simply use a normal method-based-view with multiple objects.
As the previous answer has indicated, act on the list to show only the elements belonging to the user.
Then in the update view you can limit the queryset which is used to pick the object by overriding
def get_queryset(self):
qs = super(YourUpdateView, self).get_queryset()
return qs.filter(user=self.request.user)

Django REST framework filtering views

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.

Categories

Resources