How to access column from another table in list_display? - django

I have these models:
class Orders(models.Model):
order_id = models.AutoField(primary_key=True)
user_id = models.IntegerField()
status = models.CharField(max_length=100)
created_at = models.CharField(max_length=100)
class Meta:
managed = False
db_table = 'orders'
class OrderItems(models.Model):
order_id = models.ForeignKey(Orders, models.DO_NOTHING)
quantity = models.IntegerField()
class Meta:
managed = False
db_table = 'order_items'
Now suppose I want list_display of the orders model to show the columns
order_id, user_id, status, created_at, quantity
I know I could do something like this:
class OrdersAdmin(admin.ModelAdmin):
list_display = ('id', 'user_id', 'status', 'created_at', 'get_quantity')
class Meta:
model = Orders
def get_quantity(self, obj):
result = XXX
return result.get('quantity')
get_quantity.short_description = 'Quantity'
admin.site.register(Order, OrderAdmin)
How can I access the information I need at XXX?

So you need to follow the reverse foreign key set for Orders (which btw should be named in singular).
So something like this:
orderitems = obj.orderitems_set.all()
sum = 0
for orderitem in orderitems:
sum = sum + orderitem.quantity
return sum
You can set related_name to rename the reverse set:
OrderItems(models.Model):
order_id = models.ForeignKey(Orders, models.DO_NOTHING, related_name="order_items")
and call it like order.order_items.all()
To order in OrdersAdmin:
def get_queryset(self,request):
qs = super(OrdersAdmin,self).get_queryset(request)
return qs.annotate(get_quantity=Sum('order_items__quantity'))
def get_quantity(self, obj):
return obj.order_items.all.aggregate(Sum('quantity'))
get_quantity.admin_order_field = 'get_quantity'

Related

Filter by fields from foreignKey relationships

I got a bunch of models and some of them are connected (by foreign-key relationships) and I wrote a serializer which allows me to print out all of the connected fields that I want, and leave out what I do not want to see. Great. Now I also have a basic filter, which uses the model (PmP) which contains all the foreignkeys, but now I want to add another filter for a field (field name e from PmPr Model) from a different Model, one that is read in via foreignkey connection (li in Model PmP connects to model PmL containing field pro which connects to model PmPr where the field e is). But I dont know how to do that and as far as I can see, I cant set two filter_classes inside my view (PmPLListView)?! And I dont know how to access the field via the foreignkey relation. So how do I go about this? If I can access the e field from PmPr Model via my existing filter - than that is also fine with me, I dont necessary want two filter classes (if even possible). It was just me first thought. (btw. sorry about the strange names, but unfortunately I'm not allowed to write the real names)
these are my models (at least the relevant ones):
class PmP(models.Model):
created_at = models.DateTimeField()
pr = models.DecimalField(max_digits=6, decimal_places=2)
li = models.ForeignKey(PmL, models.DO_NOTHING)
se = models.ForeignKey('PmSe', models.DO_NOTHING)
class Meta:
managed = False
db_table = 'pm_p'
class PmL(models.Model):
u = models.TextField()
pro = models.ForeignKey('PmPr', models.DO_NOTHING)
sh = models.ForeignKey('PmS', models.DO_NOTHING)
active = models.IntegerField()
class Meta:
managed = False
db_table = 'pm_l'
class PmSe(models.Model):
name = models.TextField()
s_i_id = models.TextField(blank=True, null=True)
sh = models.ForeignKey('PmS',
models.DO_NOTHING,
blank=True,
null=True)
class Meta:
managed = False
db_table = 'pm_se'
class PmPr(models.Model):
name = models.TextField()
e = models.CharField(max_length=13)
created_at = models.DateTimeField()
cus = models.ForeignKey(PmC, models.DO_NOTHING)
u_v_p = models.DecimalField(max_digits=10,
decimal_places=2,
blank=True,
null=True)
cf = models.IntegerField(blank=True, null=True)
s_k_u = models.IntegerField(blank=True, null=True)
class Meta:
managed = False
db_table = 'pm_pr'
this is what my serializer looks like:
class PmPLSerializer(serializers.ModelSerializer):
# id = serializers.SerializerMethodField('get_l_id')
u = serializers.SerializerMethodField('get_l_u')
sh = serializers.SerializerMethodField('get_sh_name')
name = serializers.SerializerMethodField('get_pro_name')
e = serializers.SerializerMethodField('get_pro_e')
u_v_p = serializers.SerializerMethodField('get_pro_u_v_p')
s_k_u = serializers.SerializerMethodField('get_pro_s_k_u')
se = serializers.SerializerMethodField('get_se_name')
pr = serializers.SerializerMethodField('get_pr')
created_at = serializers.SerializerMethodField('get_created_at')
class Meta:
model = PmP
# fields = '__all__'
fields = ('u', 'sh', 'name', 'e', 's_k_u', 'u_v_p', 'pr',
'created_at', 'se')
depth = 2
def get_l_id(self, obj):
return obj.li.id
def get_l_u(self, obj):
return obj.li.u
def get_sh_name(self, obj):
return obj.li.sh.name
def get_pro_name(self, obj):
return obj.li.pro.name
def get_pro_e(self, obj):
return obj.li.pro.e
def get_pro_u_v_p(self, obj):
return obj.li.pro.u_v_p
def get_pro_s_k_u(self, obj):
return obj.li.pro.s_k_u
def get_se_name(self, obj):
return obj.se.name
def get_pr(self, obj):
return obj.pr
def get_created_at(self, obj):
return obj.created_at
this is my filter class:
class PmPFilter(rfilters.FilterSet):
class Meta:
model = PmP
fields = [
"created_at",
"pr",
]
for field in ["pr"]:
exec(f'min_{field} = rfilters.NumberFilter(field, lookup_expr="gte")')
exec(f'max_{field} = rfilters.NumberFilter(field, lookup_expr="lte")')
# filter by date as "is_less_than_or_equal_to"
written_to = rfilters.CharFilter(method="created_at_to", label="created_at to")
# filter by date as "is_greater_than_or_equal_to"
written_from = rfilters.CharFilter(method="created_at_from", label="created_at from")
# filter by exact date
written = rfilters.CharFilter(method="created_at_exact", label="created_at exact")
def created_at_exact(self, queryset, name, value):
year, month, day, hour, minute, second = self.parse_date(value)
cdate = datetime(year, month, day, hour, minute, second)
return queryset.filter(created_at=cdate)
def created_at_to(self, queryset, name, value):
year, month, day, hour, minute, second = self.parse_date(value)
cdate = datetime(year, month, day, hour, minute, second)
return queryset.filter(created_at__lte=cdate)
def created_at_from(self, queryset, name, value):
year, month, day, hour, minute, second = self.parse_date(value)
cdate = datetime(year, month, day, hour, minute, second)
return queryset.filter(created_at__gte=cdate)
def parse_date(self, value):
return (
parser.parse(value).year,
parser.parse(value).month,
parser.parse(value).day,
parser.parse(value).hour,
parser.parse(value).minute,
parser.parse(value).second,
)
and finally, this is my view:
class PmPLListView(generics.ListAPIView):
queryset = PmP.objects.all()
serializer_class = PmPLSerializer
filter_backends = (DjangoFilterBackend, SearchFilter, OrderingFilter)
ordering_fields = ["created_at", "pr"]
filter_class = PmPFilter
fields = ("created_at", "pr")
filter_fields = fields
search_fields = fields
def get_queryset(self):
"""
This view should return a list of all data
"""
return PmP.objects.filter()
oh I got it! I can access the foreign relationship with two underscores. So I modified my Filter class to this:
class PmPFilter(rfilters.FilterSet):
class Meta:
model = PmPrice
fields = [
"created_at",
"pr",
"li__pro__e",
]
...
and inside my PmPLListView view I also added the double underscores to access the field:
class PmPLListView(generics.ListAPIView):
queryset = PmP.objects.all()
serializer_class = PmPLSerializer
filter_backends = (DjangoFilterBackend, SearchFilter, OrderingFilter)
ordering_fields = ["created_at", "pr"]
filter_class = PmPFilter
fields = ("created_at", "pr", "li__pro__e")
filter_fields = fields
search_fields = fields
now I can filter by field e

How to filter results and manytomany childs?

I am making a web page that display a "Kanban".
I have columns as well as cards.
I want to filter the results according to a field (priority) in the cards (Sale).
My models:
class PipelineColumn(models.Model):
name = models.CharField(_("Name"), max_length=80)
order = models.PositiveIntegerField(_("Order of column"), unique=True)
default_probability = models.PositiveSmallIntegerField(_("probability"))
class Meta:
verbose_name = _("PipelineColumn")
verbose_name_plural = _("PipelineColumns")
ordering = ["order"]
def __str__(self):
return self.name
class Sale(models.Model):
name = models.CharField(_("name"), max_length=255)
expected_income = models.FloatField(_("expected income"))
probability = models.PositiveSmallIntegerField(_("probability"))
contact = models.ForeignKey(
"crm.Person",
verbose_name=_("person"),
related_name="sale_person",
on_delete=models.PROTECT,
)
date = models.DateField(_("date"), auto_now_add=True)
scheduled_closing_date = models.DateField(_("scheduled closing date"))
priority = models.PositiveSmallIntegerField(_("priority"), default=0)
column = models.ForeignKey(
"crm.PipelineColumn",
verbose_name=_("column"),
related_name="sale_column",
on_delete=models.CASCADE,
)
class Meta:
verbose_name = _("Sale")
verbose_name_plural = _("Sales")
ordering = ["scheduled_closing_date", "-priority"]
def __str__(self):
return self.name
For example, I would like to display only the columns and cards that have a priority of 2. If I try with PipelineColumn.objects.filter(sale_column__priority=2) it filters the columns well, only the columns with a sale with a priority equal to 2 are displayed. On the other hand, in the displayed columns all the sales are displayed even those whose priority is not equal to 2.
I would also like to be able to filter the sales returned by my query and get only the one whose priority is equal to 2.
For the moment I use the django-url-filter module to do this filtering.
My viewset:
class PipelineColumnViewSet(viewsets.ModelViewSet):
"""
A simple ViewSet for listing or retrieving PipelineColumn.
"""
permission_classes = [permissions.IsAuthenticated]
serializer_class = PipelineColumnSerializer
filter_backends = [
DjangoFilterBackend,
filters.SearchFilter,
filters.OrderingFilter,
]
search_fields = [
"sale_column__name",
"sale_column__contact__last_name",
"sale_column__contact__first_name",
]
filter_fields = ["is_done", "sale_column"]
ordering = ["order"]
def get_queryset(self):
total = PipelineColumn.objects.aggregate(
total=Sum("sale_column__expected_income")
)["total"]
return (
PipelineColumn.objects.all()
.annotate(total_ca=Sum("sale_column__expected_income"))
.annotate(percentage_ca=(Sum("sale_column__expected_income") * 100 / total))
.prefetch_related("sale_column")
)
How to filter the sales in addition to the columns?

KeyError: 'id' in django rest framework when trying to use update_or_create() method

I am trying to update the OrderItem model using update_or_create() method. OrderItem model is related to the Order model with many to one relationship ie with a Foreignkey.
I am trying to query the orderitem object using id and update the related fields using default as you can see, but got this error.
My models:
class Order(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE, blank=True)
ordered_date = models.DateTimeField(auto_now_add=True)
ordered = models.BooleanField(default=False)
total_price = models.CharField(max_length=50,blank=True,null=True)
#billing_details = models.OneToOneField('BillingDetails',on_delete=models.CASCADE,null=True,blank=True,related_name="order")
def __str__(self):
return self.user.email
class Meta:
verbose_name_plural = "Orders"
ordering = ('-id',)
class OrderItem(models.Model):
#user = models.ForeignKey(User,on_delete=models.CASCADE, blank=True)
order = models.ForeignKey(Order,on_delete=models.CASCADE, blank=True,null=True,related_name='order_items')
item = models.ForeignKey(Product, on_delete=models.CASCADE,blank=True, null=True)
order_variants = models.ForeignKey(Variants,on_delete=models.CASCADE,blank=True,null=True)
quantity = models.IntegerField(default=1)
ORDER_STATUS = (
('To_Ship', 'To Ship',),
('Shipped', 'Shipped',),
('Delivered', 'Delivered',),
('Cancelled', 'Cancelled',),
)
order_item_status = models.CharField(max_length=50,choices=ORDER_STATUS,default='To_Ship')
My view:
class UpdateOrderView(UpdateAPIView):
permission_classes = [AllowAny]
queryset = Order.objects.all()
serializer_class = OrderUpdateSerializer
My serializers:
class OrderUpdateSerializer(serializers.ModelSerializer):
order_items = OrderItemUpdateSerializer(many=True)
billing_details = BillingDetailsSerializer()
class Meta:
model = Order
fields = ['id','ordered','order_status','order_items','billing_details']
def update(self, instance, validated_data):
instance.order_status = validated_data.get('order_status')
instance.ordered = validated_data.get('ordered')
#billing_details_logic
billing_details_data = validated_data.pop('billing_details',None)
if billing_details_data is not None:
instance.billing_details.address = billing_details_data['address']
instance.billing_details.save()
#order_items_logic
instance.save()
order_items_data = validated_data.pop('order_items')
# print(order_items_data)
#instance.order_items.clear()
for order_items_data in order_items_data:
oi, created = OrderItem.objects.update_or_create(
id= order_items_data['id'],
defaults={
'quantity' : order_items_data['quantity'],
'order_item_status': order_items_data['order_item_status']
}
)
super().update(instance,validated_data)
return oi
Updated serializer:
for order_item_data in order_items_data:
oi, created = instance.order_items.update_or_create(
id= order_item_data['id'],
defaults={
'quantity' : order_item_data['quantity'],
'order_item_status': order_item_data['order_item_status']
}
)
The order_items data are sent like this.
order_items_data is a list.
Then you iterate over it with the same variable name.
for order_items_data in order_items_data:
Just rename it to something like
for order_data in order_items_data:
and there will be an id in your order_data.
I wasn't getting the id on the OrderedDict also, so I've added a id = serializers.IntegerField(required=False) on the serializer and put that id into the update_or_create method:
for obj in data:
Model.objects.update_or_create(
pk=obj.get('id', None), ...
)

django-rest-framework aggregated sum fields

serializers.py
class BuildPlanNewSerializer(serializers.ModelSerializer):
StatusName = serializers.CharField(source='BuildPlanStatusID.StatusName', read_only=True)
# build_plan_list_new = serializers.StringRelatedField(many=True, read_only=True)
total_build_plan_list = serializers.SerializerMethodField()
def get_total_build_plan_list(self, language):
return language.build_plan_list_new.count()
class Meta:
model = BuildPlanNew
fields = ('StatusName', 'total_build_plan_list')
class BuildPlanListNewSerializer(serializers.ModelSerializer):
sku = serializers.CharField(source='ProductID.sku', read_only=True)
class Meta:
model = BuildPlanListNew
fields = "__all__"
models.py
class BuildPlanNew(models.Model):
emp_id = models.ForeignKey(Employee, on_delete=models.CASCADE, null=True, blank=True)
StartDate = models.DateTimeField()
EndDate = models.DateTimeField()
BuildPlanStatusID = models.ForeignKey(GlobalStatus, on_delete=models.CASCADE)
class BuildPlanListNew(models.Model):
BuildPlanID = models.ForeignKey(BuildPlanNew, on_delete=models.CASCADE, null=True, blank=True, related_name="build_plan_list_new")
ProductID = models.ForeignKey(Product, on_delete=models.CASCADE)
TotalPlanQty = models.IntegerField()
TotalBuiltQty = models.IntegerField())
QtyPreset = models.IntegerField(default=None, max_length=256)
Objective = models.IntegerField(default=None, max_length=256)
QtyAssigned = models.IntegerField(default=None, max_length=256)
view.py
class BuildPlanNewView(viewsets.ModelViewSet):
queryset = BuildPlanNew.objects.all()
serializer_class = BuildPlanNewSerializer
class BuildPlanListNewView(viewsets.ModelViewSet):
queryset = BuildPlanListNew.objects.all()
serializer_class = BuildPlanListNewSerializer
Result i am getting:
[{
"StatusName": "1234",
"total_build_plan_list": 0
}]
Result i am expecting:
[{
"StatusName": "1234",
"total_build_plan_list": 0,
"QtyPreset_count":20,
"Objective_count":30
}]
Here i wants to fetch aggregated sum and average from foreign key table.
Need QtyPreset_count sum as QtyPreset_count
Need Objective_count sum as as Objective_count
I have shared my models views and serializers
Please have a look
just add fields to your serializer like you have done so far
from django.db.models import Sum, Avg
class BuildPlanNewSerializer(serializers.ModelSerializer):
StatusName = serializers.CharField(source='BuildPlanStatusID.StatusName', read_only=True)
# build_plan_list_new = serializers.StringRelatedField(many=True, read_only=True)
total_build_plan_list = serializers.SerializerMethodField()
QtyPreset_count = serializers.SerializerMethodField()
Objective_count = serializers.SerializerMethodField()
def get_total_build_plan_list(self, language):
return language.build_plan_list_new.count()
def get_QtyPreset_count(self, language):
return language.build_plan_list_new.aggregate(Sum('QtyPreset'))
# return language.build_plan_list_new.aggregate(Avg('QtyPreset')) if you need it's average
def get_Objective_count(self, language):
return language.build_plan_list_new.aggregate(Sum('Objective'))
class Meta:
model = BuildPlanNew
fields = ('StatusName', 'total_build_plan_list')

Django add aggregate operation to a query result

I'm trying to do an aggregate operation between two tables using Django, my models are:
class Cusinetype(models.Model):
hometype_en = models.TextField()
active = models.BooleanField()
hometype_es = models.TextField(blank=True, null=True)
class Meta:
managed = False
db_table = 'cusinetype'
class Foodpreferences(models.Model):
id_client = models.ForeignKey(Client, models.DO_NOTHING, db_column='id_client')
id_cusinetype = models.ForeignKey(Cusinetype, models.DO_NOTHING, db_column='id_cusinetype')
created_at = models.DateTimeField()
class Meta:
managed = False
db_table = 'foodpreferences'
The query that I'm trying to build is:
SELECT
ct.id,
ct.hometype_en,
ct.hometype_es
,
((SELECT COUNT(*)
FROM foodpreferences fp
WHERE fp.id_cusinetype = ct.id AND fp.id_client = 3 ) > 0 ) selected
FROM
Cusinetype ct
I'm trying to generate a model, to store the information of those tables in a single one query, but anything works.
Someone has an idea about how to do it?
serializers.py
class PreferencesSerializer(serializers.ModelSerializer):
selected = serializers.IntegerField()
class Meta:
model = Cusinetype
fields = ('id', 'trucktype_en', 'trucktype_es', 'selected')
views.py
qs = Cusinetype.objects.filter().filter(active = True)
qs = qs.annotate(
selected=Sum(Case(
When(foodpreferences__id_client=3, then=1),
output_field=IntegerField()
))
)
serializers = PreferencesSerializer(qs, many = True)
return Response({ "result": serializers.data })