Мне необходимо включить поле, присутствующее в родительской таблице, в дочернюю таблицу. Мне нужно сформировать набор запросов, чтобы получить упомянутый список записей для моего мобильного приложения. См. ниже мои модели, чтобы иметь четкое представление.
#models.py
class Client(models.Model):
id = models.AutoField(primary_key=True)
name = models.CharField(max_length=100)
contact = models.CharField(max_length=10, unique=True)
email = models.EmailField(null=True)
address = models.TextField()
modified_at = models.DateTimeField(auto_now=True)
created_at = models.DateTimeField(auto_now_add=True)
class Meta:
db_table = 'clients'
ordering = ['id']
verbose_name = 'Client'
verbose_name_plural = 'Clients'
def __str__(self):
return str(self.id) + ". " + self.name
class Rent(models.Model):
id = models.AutoField(primary_key=True)
address = models.TextField()
rent_amount = models.IntegerField()
deposit_amount = models.IntegerField()
rent_date = models.DateField()
document = models.TextField(null=True)
remarks = models.TextField(null=True)
created_at = models.DateTimeField(auto_now_add=True)
modified_at = models.DateTimeField(auto_now=True)
client_id = models.IntegerField()
class Meta:
db_table = 'rent'
verbose_name = 'Rent'
verbose_name_plural = 'Rents'
def __str__(self):
return self.id
def get_client_name(self):
client = Client.objects.get(pk=self.client_id)
return client.name
class RentSchedule(models.Model):
id = models.AutoField(primary_key=True)
rent_due_date = models.DateField()
paid_amount = models.IntegerField(default=0)
remaining_amount = models.IntegerField()
payment_status = models.IntegerField(choices=[(0, 'Unpaid'), (1, 'Paid')], default=0)
created_at = models.DateTimeField(auto_now_add=True)
rent_id = models.IntegerField()
class Meta:
db_table = 'rent_schedule'
verbose_name = 'Rent Schedule'
verbose_name_plural = 'Rent Schedule'
def __str__(self):
return self.id
def get_client_name(self):
rent = Rent.objects.get(pk=self.rent_id)
client = Client.objects.get(pk=rent.client_id)
return client.name
Ниже приведен мой класс сериализатора.
#serializers.py
class RentListSerializer(serializers.ModelSerializer):
client_name = serializers.CharField(source='get_client_name', required=False)
remaining_amount = serializers.IntegerField(read_only=True)
payment_status = serializers.ChoiceField(choices=[0, 1], default=0, write_only=True, error_messages = {'invalid_choice': 'Options are 0 or 1'})
due_date = serializers.DateField(format = "%d-%m-%Y", source='rent_due_date', read_only=True)
date_filter = serializers.DateField(input_formats=['%m-%Y'], default=datetime.now().strftime('%m-%Y'), write_only=True, required=False)
sort_by_client = serializers.ChoiceField(choices=["asc", "desc"], write_only=True, required=False, error_messages = {'invalid_choice': 'Options are asc or desc'})
sort_by_due_date = serializers.ChoiceField(choices=["asc", "desc"], write_only=True, required=False, error_messages = {'invalid_choice': 'Options are asc or desc'})
sort_by_remaining_amount = serializers.ChoiceField(choices=["asc", "desc"], write_only=True, required=False, error_messages = {'invalid_choice': 'Options are asc or desc'})
class Meta:
model = RentSchedule
fields = ['id', 'client_name', 'paid_amount', 'remaining_amount', 'due_date', 'payment_status', 'date_filter', 'sort_by_client', 'sort_by_due_date', 'sort_by_remaining_amount']
def to_representation(self, instance):
representation = super().to_representation(instance)
representation['payment_status'] = "Paid" if instance.remaining_amount == 0 else "Pending"
representation.pop('due_date') if instance.remaining_amount == 0 else None
return representation
Теперь, наконец, ниже показано мое представление на основе классов, в котором необходимо изменить набор запросов.
#views.py
class RentList(GenericAPIView):
authentication_classes = [TokenAuthentication]
permission_classes = [IsAuthenticated]
queryset = RentSchedule.objects.all()
serializer_class = RentListSerializer
class CustomPagination(PageNumberPagination):
page_size = 100
page_size_query_param = 'page_size'
max_page_size = 100
def get_paginated_response(self, data):
return Response({
'statusCode': 200,
'records': self.page.paginator.count,
'data': data,
'current_page': self.page.number,
'total_pages': self.page.paginator.num_pages
})
pagination_class = CustomPagination
def initial(self, request, *args, **kwargs):
super().initial(request, *args, **kwargs)
self.serializer = self.get_serializer(data=request.data)
if not self.serializer.is_valid():
raise ValidationError(detail=self.serializer.errors)
def filter_queryset(self, queryset, request):
serializer = self.serializer_class()
payment_status = request.data.get('payment_status')
client_name = request.data.get('client_name')
date_filter = request.data.get('date_filter')
sort_by_client = request.data.get('sort_by_client')
sort_by_due_date = request.data.get('sort_by_due_date')
sort_by_remaining_amount = request.data.get('sort_by_remaining_amount')
"""
This is to be modified as below is an incorrect join ORM.
queryset = queryset.annotate(
client_name=First(
Subquery(
Client.objects.filter(
id=Subquery(
Rent.objects.filter(
id=Subquery(
RentSchedule.objects.filter(
rent_id=OuterRef('id')
).values('rent_id')
)
).values('client_id')
)
).values('name')
)
)
)
"""
date_filter = date_filter if date_filter else serializer.fields['date_filter'].get_default()
month, year = date_filter.split('-')
queryset = queryset.filter(rent_due_date__month=month, rent_due_date__year=year)
payment_status = payment_status if payment_status is not None else serializer.fields['payment_status'].get_default()
queryset = queryset.filter(payment_status=payment_status)
if client_name:
queryset = queryset.filter(rent_id__in=Rent.objects.filter(client_id__in=Client.objects.filter(name__icontains=client_name).values('id')).values('id'))
if sort_by_client:
if sort_by_client.lower() == 'asc':
queryset = queryset.order_by('client_name')
elif sort_by_client.lower() == 'desc':
queryset = queryset.order_by('-client_name')
if sort_by_due_date:
if sort_by_due_date.lower() == 'asc':
queryset = queryset.order_by('rent_due_date')
elif sort_by_due_date.lower() == 'desc':
queryset = queryset.order_by('-rent_due_date')
if sort_by_remaining_amount:
if sort_by_remaining_amount.lower() == 'asc':
queryset = queryset.order_by('remaining_amount')
elif sort_by_remaining_amount.lower() == 'desc':
queryset = queryset.order_by('-remaining_amount')
return queryset
def post(self, request, *args, **kwargs):
queryset = self.filter_queryset(self.get_queryset(), request)
paginator = self.pagination_class()
paginated_queryset = paginator.paginate_queryset(queryset, request, view=self)
serializer = self.get_serializer(paginated_queryset, many=True)
return paginator.get_paginated_response(serializer.data)
def handle_exception(self, exc):
if isinstance(exc, ValidationError):
response = Response(
data = {
"status_code": 400,
"message": exc.detail
},
status=400
)
return response
return super().handle_exception(exc)
Моя цель — включить поле name
из модели Client
, которая является родительской моделью, в набор запросов, упомянутый в комментариях. Я хочу, чтобы он имел псевдоним client_name
, чтобы он отображался в записях под этим ключом.
queryset = RentSchedule.objects.all()
Поскольку модели RentSchedule
и Rent
не имеют типа данных внешнего ключа из-за ограничений моего проекта, я не мог использовать метод select_related
набора запросов для достижения того же. Я могу думать только о методе annonate
, который потребует сложного подзапроса.
Как мне добиться этого, чтобы я мог сортировать набор запросов через поле client_name
в порядке «по возрастанию» или «по убыванию»?
Спасибо всем заранее.
Если вам нужно, чтобы поле было в наборе запросов, annotate
может быть неизбежным. Создавать подзапросы будет проще, если вы разобьете их.
Я немного подзабыл о создании подзапроса, но это должно быть довольно близко. Возможно, вам придется переместить объявления .values()
из определения подзапроса и в выражение подзапроса родительского запроса, если они не работают «из коробки».
# Extract `name` from the client
client_query = Client.objects.filter(id=OuterRef('client_id')).values('name')
rent_query = Rent.objects
# Tack on `client_name` from the first subquery
.annotate(client_name=Subquery(client_query))
# Select the relevant Rent objects and pull out `client_name`
.filter(id=OuterRef('rent_id')).values('client_name')
# Tack on `client_name` from the second subquery to the final queryset
queryset = RentSchedule.objects.annotate(client_name=Subquery(rent_query))
Да, я не уверен, как здесь будет выглядеть результирующий SQL. Необработанный SQL может быть лучшим вариантом в зависимости от того, как Django ORM обрабатывает вложенные подзапросы.
Работает так, как должно. Ты сделал это. Большое спасибо. :)
Вы отвечаете правильно. Я просто хотел бы подтвердить, что аннотация
IS
неизбежна. А вложенныйSubqueries
означает беду. Возможно, лучше было бы создать новое поле модели со сценарием управления для его заполнения, смешанным с заданиями cron. (В случае, если об обновлении приложения не может быть и речи)