I'm trying to re-factor my Django app. Here is the repeated code i found annoying :
class EducationInfoViewSet(viewsets.ModelViewSet):
queryset = pf.education_info.objects.all()
serializer_class = EducationInfoSerializer
permission_classes = (VisionUserPermissions, )
def get_queryset(self):
model = pf.education_info
identity = self.request.query_params.get('username',None)
queryset = model.objects.all()
user = User.objects.none()
if identity is not None:
user = User.objects.get(username=identity)
student = get_object_or_404(dbd.user_type, username=user)
queryset = model.objects.filter(username=student)
if not permission_check(self.request,user):
return User.objects.none()
return queryset
class FamilyInfoViewSet(viewsets.ModelViewSet):
queryset = pf.family_info.objects.all()
serializer_class = FamilyInfoSerializer
permission_classes = (VisionUserPermissions, )
def get_queryset(self):
model = pf.family_info
identity = self.request.query_params.get('username',None)
queryset = model.objects.all()
user = User.objects.none()
if identity is not None:
user = User.objects.get(username=identity)
student = get_object_or_404(dbd.user_type, username=user)
queryset = model.objects.filter(username=student)
if not permission_check(self.request,user):
return User.objects.none()
return queryset
So my "get_queryset" functions are identical other than one variable, and i have multiple classes like these two. How should I implement this without repeating myself?
Thanks in advance!
Create a mixing class
class QuerysetMixin(viewsets.ModelViewSet):
def get_queryset(self):
model = getattr(pf, self.field_name)
identity = self.request.query_params.get('username',None)
queryset = model.objects.all()
user = User.objects.none()
if identity is not None:
user = User.objects.get(username=identity)
student = get_object_or_404(dbd.user_type, username=user)
queryset = model.objects.filter(username=student)
if not permission_check(self.request,user):
return User.objects.none()
return queryset
and inherit it
class FamilyInfoViewSet(viewsets.ModelViewSet, QuerysetMixin):
queryset = pf.family_info.objects.all()
serializer_class = FamilyInfoSerializer
permission_classes = (VisionUserPermissions, )
field_name = 'family_info'
Related
Got in my model serializer such field like is_favorited and others
is_favorited = serializers.SerializerMethodField()
def get_is_favorited(self, obj):
user = self.context['request'].user
if user.is_anonymous:
return False
qs = Favorite.objects.filter(user=user, recipe=obj)
return len(qs) > 0
my model viewset contains
queryset = Recipe.objects.all()
filter_backends = [DjangoFilterBackend]
filterset_fields = (
'is_favorited',
)
in Postman i get this bad request when trying to filter by this boolean field
decided to use get_queryset in order to filter simple queries.
I have the following Django urls/views and Models:
Models.py:
ORDER_COLUMN_CHOICES = Choices(
('0', 'id'),
('1', 'code'),
('2', 'code_type'),
('3', 'created'),
('4', 'updated'),
('5', 'valid'),
)
class Identifier(TimeStampMixin, models.Model):
code_type = models.CharField(max_length=10, null=True)
code = models.CharField(max_length=12)
account = models.ForeignKey(Account, on_delete=models.CASCADE, null=True)
actflag = models.CharField(max_length=1, blank=True)
valid = models.BooleanField(default=False)
def __str__(self):
return self.code
class Meta:
db_table = "portfolio_identifier"
def query_identifier_by_args(**kwargs):
draw = int(kwargs.get('draw', None)[0])
length = int(kwargs.get('length', None)[0])
start = int(kwargs.get('start', None)[0])
search_value = kwargs.get('search[value]', None)[0]
order_column = kwargs.get('order[0][column]', None)[0]
order = kwargs.get('order[0][dir]', None)[0]
order_column = ORDER_COLUMN_CHOICES[order_column]
# django orm '-' -> desc
if order == 'desc':
order_column = '-' + order_column
queryset = Identifier.objects.all()
total = queryset.count()
if search_value:
queryset = queryset.filter(Q(id__icontains=search_value) |
Q(code__icontains=search_value) |
Q(code_type__icontains=search_value) |
Q(created__icontains=search_value) |
Q(updated__icontains=search_value) |
Q(valid__icontains=search_value))
count = queryset.count()
queryset = queryset.order_by(order_column)[start:start + length]
return {
'items': queryset,
'count': count,
'total': total,
'draw': draw
}
Urls.py
from . import views
from rest_framework.routers import DefaultRouter
from apps.portfolio.views import IdentifierViewSet
router = DefaultRouter()
router.register(r'portfolio', IdentifierViewSet)
urlpatterns = [
path('portfolios/', views.portfolios, name="portfolios"),
path('portfolio/<str:pk>/', views.portfolio, name="portfolio"),
path('api/', include(router.urls)),
]
Views.py
def portfolio(request, pk):
portfolio = Account.objects.get(id=pk)
identifiers = Identifier.objects.filter(account=pk)
context = {"portfolio": portfolio, "identifiers": identifiers}
return render(request, 'portfolio.html', context)
class IdentifierViewSet(viewsets.ModelViewSet):
queryset = Identifier.objects.all()
serializer_class = IdentifierSerializer
authentication_classes = []
def get_queryset(self):
account_pk = self.kwargs["pk"]
return super().get_queryset().filter(account=account_pk)
def list(self, request, **kwargs):
try:
identifier = query_identifier_by_args(**request.query_params)
serializer = IdentifierSerializer(identifier['items'], many=True)
result = dict()
result['data'] = serializer.data
result['draw'] = identifier['draw']
result['recordsTotal'] = identifier['total']
result['recordsFiltered'] = identifier['count']
return Response(result, status=status.HTTP_200_OK, template_name=None, content_type=None)
except Exception as e:
return Response(e, status=status.HTTP_404_NOT_FOUND, template_name=None, content_type=None)
Within the Views I have a Class based view IdentifierViewSet with the following queryset line queryset = Identifier.objects.all() which retreievs all data from the db model however I would like to only retrieve queryset based on portfolio associated with the user's account which works using the following lines of code taken from the function based view portfolio:
portfolio = Account.objects.get(id=pk)
identifiers = Identifier.objects.filter(account=pk)
I was unable to pass the pk from urls to this class based view but was able to do so with the function based view.
How can I go about passing the above object queries to within the Class based view to replace the queryset in the Class based view queryset = Identifier.objects.all()?
To do that you can override the get_queryset method from the Django ModelViewSet.
def get_queryset(self):
portfolio = Account.objects.get(id=self.request.account.pk)
#... do something
identifiers = Identifier.objects.filter(account=self.request.account.pk)
return identifiers
There are list(), retrieve(), create(), update(), partial_update(), destroy() functions and the pk parameter can be passed to four functions except list and create.
For example, for PUT method of the API, you can customize the update function.
class IdentifierViewSet(viewsets.ModelViewSet):
queryset = Identifier.objects.all()
serializer_class = IdentifierSerializer
authentication_classes = []
def list(self, request, *args, **kwargs):
...
def update(self, request, pk):
# you can get the pk here
print(pk)
# you can get the object with the current `pk`
instance = self.get_object()
...
You can also customize other functions like this.
Can we pass the URL queryset parameter into serizalizer to have filitering/calculation?
here is my endpoint: ?start_date=20210312&end_date=20210317
herer is my viewset:
class BrandChartsViewSet(ModelViewSet):
serializer_class = BrandChartsSerializer
pagination_class = BrandChartsPagination
queryset = Brand.objects.all()
def get_queryset(self):
start_date = self.request.query_params.get('start_date',None)
end_date = self.request.query_params.get('end_date',None)
if start_date is None:
raise InvalidInputError()
if end_date is None:
raise InvalidInputError()
start_date_obj = datetime.strptime(start_date,'%Y%m%d')
end_date_obj = datetime.strptime(end_date,'%Y%m%d')
serializer = BrandChartsSerializer(start_date_obj,end_date_obj)
queryset = Brand.objects.all()
return queryset
here is my serizalizer:
class BrandChartsSerializer(serializers.ModelSerializer):
rankings = serializers.SerializerMethodField()
instagram = IGSerializer(allow_null=True)
facebook = FBSerializer(allow_null=True)
hashtags = BrandHashtagSerializer(allow_null=True, many=True)
# rename the json field
brand_uid = serializers.IntegerField(source='id')
brand_name = serializers.CharField(source='name')
origin = serializers.CharField(source="country")
class Meta:
model = Brand
fields = [
"brand_uid",
"brand_name",
"origin",
"rankings",
"instagram",
"facebook",
"hashtags",
]
def get_rankings(self, instance):
rank = instance.rankings.all().filter(created__gte=start_date_obj,
created__lte=end_date_obj
).order_by('created')
return BrandRankSerializer(rank, allow_null=True, many=True).data
and it will have Unresolved reference 'start_date_obj' in get_rankings, however I tried to passed the parameter into serizalizer by class BrandChartsSerializer(serializers.ModelSerializer,start_date_obj,end_date_obj)
It still giving error.
Due to my function design, I think it could not use DRF filter to handle it, could I just pass the parameter to serizalizer to do filitering? (I need to filter the rankings model in range and calculate the sum than return)
We can make use of context in such cases.
Try this.
Update the get_rankings method.
def get_rankings(self, instance):
start_date_obj = self.context.get("request").query_params.get("start_date")
end_date_obj = self.context.get("request").query_params.get("end_date")
rank = instance.rankings.all().filter(created__gte=start_date_obj,
created__lte=end_date_obj
).order_by('created')
return BrandRankSerializer(rank, allow_null=True, many=True).data
I'm writing a custom create method for my model:
class TripReportViewSet(viewsets.ModelViewSet):
permission_classes = (permissions.IsAuthenticatedOrReadOnly,)
serializer_class = TripReportSerializer
pagination_class = TripReportSetPagination
# To order by favorite count or 'top':
queryset = TripReport.objects.all().annotate(count=Count('favoriters')).order_by('-count')
#queryset = TripReport.objects.all().order_by('-pk')
filter_backends = (filters.SearchFilter, filters.OrderingFilter)
search_fields = ('=author__username', '=slug', 'countries__name', )
ordering_fields = ('pk', )
def create(self, request, **kwargs):
countries = request.POST['countries'].split(',')
countries = list(map(int, countries))
countries = Country.objects.filter(pk__in=countries)
instance = TripReport.objects.create(
author=User.objects.get(pk=request.POST['author']),
title=request.POST['title'],
content=request.POST['content'],
)
instance.countries.set(countries)
instance.save()
return HttpResponse(TripReportSerializer(instance))
I can't seem to get the right response. I want to return my serialized object, but
HttpResponse(instance)
and
HttpResponse(TripReportSerializer(instance))
is giving me the wrong result. TripReportSerializer is the one I'm using for the view.
I see two things wrong with the code:
To return the serializer data I think you should use TripReportSerializer(instance).data
Rest Framework Views generally return a Response object, which is imported from rest_framework.response.Response
Another amendment you should make is to use the views get_serializer() method so the serializer will be populated with the context (view, format, and request), which in your case would mean using this code at the end of your create method.
serializer = self.get_serializer(instance)
return Response(serializer.data)
What you have to do is, serialize the newly created Trip instance and return by using DRF's Response class
from rest_framework.response import Response
class TripReportViewSet(viewsets.ModelViewSet):
permission_classes = (permissions.IsAuthenticatedOrReadOnly,)
serializer_class = TripReportSerializer
pagination_class = TripReportSetPagination
# To order by favorite count or 'top':
queryset = TripReport.objects.all().annotate(count=Count('favoriters')).order_by('-count')
# queryset = TripReport.objects.all().order_by('-pk')
filter_backends = (filters.SearchFilter, filters.OrderingFilter)
search_fields = ('=author__username', '=slug', 'countries__name',)
ordering_fields = ('pk',)
def create(self, request, **kwargs):
countries = request.POST['countries'].split(',')
countries = list(map(int, countries))
countries = Country.objects.filter(pk__in=countries)
instance = TripReport.objects.create(
author=User.objects.get(pk=request.POST['author']),
title=request.POST['title'],
content=request.POST['content'],
)
instance.countries.set(countries)
instance.save()
# changes
serializer = TripReportSerializer(instance)
return Response(serializer.data)
I am using django_filters and I have a little problem with combining it each other.
What do I have? Basic filters, for example:
class BasicFilter(django_filters.FilterSet):
class Meta:
model = myModel
fields = []
class TimeFilter(BasicFilter):
created = django_filters.DateFromToRangeFilter(
help_text='Date from - to', label='Time'
)
class Meta(BasicFilter.Meta):
fields = ['created']
class AgentFilter(BasicFilter):
agent = django_filters.ModelMultipleChoiceFilter(
queryset=AgentClass.objects.all(), help_text=''
)
class Meta(BasicFilter.Meta):
fields = ['agent']
class SomethingElseFilter(BasicFilter):
something = django_filters.ModelMultipleChoiceFilter(
queryset=SomethingElse.objects.all(), help_text=''
)
class Meta(BasicFilter.Meta):
fields = ['something']
The user will decide which filters he wants, e.g. He will choose TimeFilter and AgentFilter, and I need to connect this basic filters to one ConnectedFilter.
Then I handle it in my views, e.g.
class MyView(ListView):
model = myModel
template_name = "filters.html"
def get_queryset(self):
qs=MyModel.objects.all()
try:
self.connected_filter = ConnectedFilter(
self.request.GET, queryset = qs)
return self.connected_filter
except:
return qs
def get_context_data(self, **kwargs):
context = super(MyView, self).get_context_data(**kwargs)
context['filter_form'] = self.connected_form.as_table()
return context
Or maybe there's some better solution.