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
Related
I have a queryset that I'm trying to serialize with my CasePartySerializer which inherits the Django REST Framework ModelSerializer class. It has a nested serializer called CaseRuleDocketSerializer.
Here is how I instantiate it.
def get_queryset(self):
self.queryset = CaseParty.objects.filter(user=self.request.user)
def get(self):
serial = CasePartySerializer(
list(
self.queryset[
self.offset:self.offset + self.limit
]
),
many=True,
context={
'tracked': self.tracked.value,
'request': self.request
}
)
Simple Enough, but what if I want to conditionally pass over and reject objects so they are not included in the finalized serial.data.
I'm sure there is probably some exception I can raise that would pass over this database object, but I'm unsure what that would be. I looked through the documentation without any luck; it's something that surprised me considering the quality of the REST Framework documentation. I'm probably missing something simple.
Here is my CasePartySerializer so you can see my conditionals. In this example you can see they are based on results from the nested serializer CaseRuleDocketSerializer output which is not available from the get_queryset method. If one of the ruledocket items is 'red' it does not need to be included in the serializer result. I know I can filter in the get_queryset method also, but it just seems it would be easier to do in the serializer itself.
class CaseRuleDocketSerializer(serializers.ModelSerializer):
table_row_color = serializers.SerializerMethodField()
class Meta:
model = CaseRuleDocket
fields = [
'unique_id',
'created_date',
'updated_date',
'date_time',
'entry',
'party'
]
def get_is_user_only_created(self, obj):
if obj.is_user_created and not obj.is_court_created:
return 'green'
elif obj.is_court_created and not obj.is_user_created:
return 'blue'
else:
return 'red'
class CasePartySerializer(serializers.ModelSerializer):
docket_of_party = CaseRuleDocketSerializer(many=True, read_only=True)
table_row_color = serializers.SerializerMethodField()
class Meta:
model = CaseParty
fields = [
'is_tracked',
'created_date',
'updated_date',
'serve_status',
'serve_status_date',
'defendant',
'plaintiff',
# CONTINUE WITH THE REST OF THE FIELDS....
]
def get_table_row_color(self, obj):
errors = [
x.table_row_color for x in self.obj.docket_party
]
if 'blue' in errors:
return 'blue'
elif 'green' in errors:
return 'green'
else:
# HERE IS WHERE I WANT TO EXCEPT AND REMOVE
# THIS CaseParty OBJECT INSTANCE
I have been using stackoverflow for many years and I have always found the answers I have needed when looking. For some reason I could not find a way to frame the question correctly. Also, I am familiar with the documentation and have read it thoroughly. If it is not suggested to filter inside of the queryset due to efficiency, readability, or some other missight on my part, please include the reason in your answer.
Let me know if you need clarification.
Thanks!
Based on your example, where you are filtering all CaseParty instances that have no related CaseTrack models, your best bet would be to update the view's get_queryset method.
Basically, rather than using queryset = CaseParty.objects.all(), you could write your own get_queryset(self) method, which will filter out the unwanted models.
To give a more concrete example:
class MyViewSet(ModelViewSet):
def get_queryset(self):
# We fetch all party instances
party_instances = CaseParty.objects.all()
# We fetch all case_tracks linked to them
case_tracks = CaseTrack.objects.get(
user=self.request.user,
case__in=party_instances
)
# We extract the party ids from case tracks
party_ids_from_casetracks = {item.case.id for item in case_tracks}
# We only keep those parties in the queryset
party_instances_with_tracks = [item for item in party_instances if item.id in party_ids_from_casetracks ]
return party_instances_with_tracks
You'll find more example in the official documentation
If filterting at the viewset level is problematic because different endpoints/views must have different filters, then simply filter then from within the view action.
As someone else mentionned in the comments, the serializer is not here to filter the data:
The view filters the data (queryset, get_queryset, filter_queryset, detail view, etc)
The serializer receives the data, validates it, create/update/destroy instances, return representation and internal values
I created a model Checkout on my project, with a CheckoutType to handle the requests, but now i need a Profile, that is basically just getting many of the fields on Checkout. The problem is that Checkout and Profile will be retrieved by users with very different permissions, and the while the first one will have the right ones, the second one must not have them. so i went with creating 2 types:
Checkout:
class CheckoutType(ModelType):
class Meta:
model = Checkout
interfaces = [graphene.relay.Node]
connection_class = CountableConnection
permissions = ['app.view_checkout']
filter_fields = {
'zone': ['exact'],
'vehicle__mark': ['exact'],
'status': ['exact']
}
Profile:
class ProfileFilter(django_filters.FilterSet):
class Meta:
model = Checkout
fields = ['zone','status']
#property
def qs(self):
# The query context can be found in self.request.
return super(ProfileFilter, self).qs.filter(salesman=self.request.user)
class ProfileType(ModelType):
class Meta:
model = Checkout
interfaces = [graphene.relay.Node]
connection_class = CountableConnection
filterset_class = ProfileFilter
The thing here is that, the first one shouldn't filter, and just be a regular schema, while the second one should filter by the user that made the request, that and the permissions is the reason i use 2, but as soon as i implemented, all the tests i did for the Checkout Type started to fail, since it seems it tries to use the ProfileType. I searched a little, and it seems that relay only allows a type per model in Django, so this approach doesn't seems possible, but i'm not sure how to overwrite the CheckoutType on another schema, or how to make a second Type with different permissions and different filters. Does someone knows if this is possible?
Just in case someone is on the same boat, i think i found a way to make it work, but with a different approach, i just modified the CheckoutType a little:
class CheckoutType(ModelType):
# Meta
#classmethod
def get_queryset(cls, queryset, info):
if info.context.user.has_perm('app.view_checkout'):
return queryset
return queryset.filter(salesman=info.context.user)
Basically here i remove the permission from the Meta, since i don't want to check that there, and then i overwrite the get_queryset() to check if the user has the perms, if that's the case, then just return the normal query, but if not just filter(And any additional thing you want to do for people without the permission). I'm not sure if there's a better way, but definitely did the job.
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.
If I define a django-rest-framework view framework as:
class ProblemViewSet(viewsets.ModelViewSet):
queryset = Problem.objects.all()
serializer_class = ProblemSerializer
#detail_route(methods=['post'], permission_classes=(permissions.IsAuthenticated,))
def submit(self, request, *args, **kwargs):
# my code here
return Response(...)
When I look at the CoreAPI schema that is defined, I find this:
problems: {
create(title, text, value, category, hint)
destroy(pk)
list([page])
partial_update(pk, [title], [text], [value], [category], [hint])
retrieve(pk)
submit(pk)
update(pk, title, text, value, category, hint)
}
I'd like the submit API endpoint to take an additional parameter, called answer, but so far I haven't figured out how to add such a custom parameter. I know I can just pass in the POST array, but that seems inelegant and un-RESTful. Any ideas?
I don't understand your question, but I think that you want to add in the input the filed answer to django-rest form or raw, you can do this adding in the serializer ProblemSerializer the next:
from rest_framework import serializers
...
class CustomerSerializer(serializers.ModelSerializer):
answer = serializers.SerializerMethodField()
class Meta:
model = Problem
fields = ['answer', 'my-normal-params',...]
def get_answer(self, problem):
if hasattr(problem, 'answer'):
request = self.context['request']
return answer
In get_answer method you will display in the json the value that you want, or you can return None (null) if you want,
If that is not your problem, please say me, I'll help you.
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.