many-to-many in list display django - python

class Product(models.Model):
products = models.CharField(max_length=256)
def __unicode__(self):
return self.products
class PurchaseOrder(models.Model):
product = models.ManyToManyField('Product')
vendor = models.ForeignKey('VendorProfile')
dollar_amount = models.FloatField(verbose_name='Price')
I have that code. Unfortunately, the error comes in admin.py with the ManyToManyField
class PurchaseOrderAdmin(admin.ModelAdmin):
fields = ['product', 'dollar_amount']
list_display = ('product', 'vendor')
The error says:
'PurchaseOrderAdmin.list_display[0]', 'product' is a ManyToManyField
which is not supported.
However, it compiles when I take 'product' out of list_display. So how can I display 'product' in list_display without giving it errors?
edit: Maybe a better question would be how do you display a ManyToManyField in list_display?

You may not be able to do it directly. From the documentation of list_display
ManyToManyField fields aren’t supported, because that would entail
executing a separate SQL statement for each row in the table. If you
want to do this nonetheless, give your model a custom method, and add
that method’s name to list_display. (See below for more on custom
methods in list_display.)
You can do something like this:
class PurchaseOrderAdmin(admin.ModelAdmin):
fields = ['product', 'dollar_amount']
list_display = ('get_products', 'vendor')
def get_products(self, obj):
return "\n".join([p.products for p in obj.product.all()])
OR define a model method, and use that
class PurchaseOrder(models.Model):
product = models.ManyToManyField('Product')
vendor = models.ForeignKey('VendorProfile')
dollar_amount = models.FloatField(verbose_name='Price')
def get_products(self):
return "\n".join([p.products for p in self.product.all()])
and in the admin list_display
list_display = ('get_products', 'vendor')

This way you can do it, kindly checkout the following snippet:
class Categories(models.Model):
""" Base category model class """
title = models.CharField(max_length=100)
description = models.TextField()
parent = models.ManyToManyField('self', default=None, blank=True)
when = models.DateTimeField('date created', auto_now_add=True)
def get_parents(self):
return ",".join([str(p) for p in self.parent.all()])
def __unicode__(self):
return "{0}".format(self.title)
And in your admin.py module call method as follows:
class categories(admin.ModelAdmin):
list_display = ('title', 'get_parents', 'when')

If you want to save extra queries, you can use prefetch_related in the get_queryset method like below:
class PurchaseOrderAdmin(admin.ModelAdmin):
fields = ['product', 'dollar_amount']
list_display = ('get_products', 'vendor')
def get_queryset(self, request):
qs = super().get_queryset(request)
return qs.prefetch_related('product')
def get_products(self, obj):
return ",".join([p.products for p in obj.product.all()])
According to the Docs, In this way, there would be just one extra query needed to fetch related Product items of all PurchaseOrder instances instead of needing one query per each PurchaseOrder instance.

Related

Filter field of manytomany field by a list

I would like to do the following with Django REST Framework:
Filter results based on a field of a manytomany field.
The query would look like this:
https://endpoint.com/api/artwork/?having_style=Modern,Contemporary
I would expect the result to contain all ArtWork objects which contain a relation to a Style object with name "Modern", "Contemporary" or both.
The code below is not working and I don't know why.
models.py
class Style(models.Model):
name = models.CharField(validators=[validate_style], max_length=100, unique=True)
class ArtWork(models.Model):
styles = models.ManyToManyField(Style, default=None)
filters.py
class ArtWorkFilter(filters_rest.FilterSet):
having_style = django_filters.Filter(field_name="styles__name", lookup_expr='in')
class Meta:
model = ArtWork
fields = ['having_style']
class StyleSerializer(serializers.ModelSerializer):
class Meta:
model = Style
fields = ('name',)
class ArtWorkSerializer(serializers.ModelSerializer):
styles = StyleSerializer(many=True)
class Meta:
model = ArtWork
fields = ('styles'/)
views.py
class ArtWorkViewSet(viewsets.ModelViewSet):
permission_classes = []
queryset = ArtWork.objects.all()
serializer_class = ArtWorkSerializer
filter_backends = [filters_rest.DjangoFilterBackend,]
filterset_class= ArtWorkFilter
pagination_class = CursorSetPagination
Thank you in advance!
Solution
I solved it by changing the ArtWorkFilter to
filters.py
class ArtWorkFilter(filters_rest.FilterSet):
having_style = django_filters.Filter(field_name="styles__name", lookup_expr='in')
class Meta:
model = ArtWork
fields = ['having_style']
def filter_by_style_name(self, queryset, name, value):
list_styles = value.split(',')
return queryset.filter(styles__name__in=list_styles)
Try adding method param in Filter declaration. Something like:
class ArtWorkFilter(filters_rest.FilterSet):
having_style = django_filters.Filter(field_name="styles__name", lookup_expr='in')
class Meta:
model = ArtWork
fields = ['having_style']
def filter_by_style_name(self, queryset, name, value):
list_styles = value.split(',')
return queryset.filter(styles__name__in=list_styles)
CartItem.objects.filter(cart=cart, product=product, attribute__in=attribute_list).annotate(num_attr=Count('attribute')).filter(num_attr=len(attribute_list))

How to populate an inline admin form using a GROUP BY query

I have a TabularInline class, StockPartInlines in the inline form view I am trying to populate with a query that GROUPs based on two fields (container_id, expiration_date) and adds an additional field that is a SUM of an existing field.
class Stock(models.Model):
part = models.ForeignKey(Part,
on_delete=models.PROTECT,
related_name='stock')
container = models.ForeignKey(StorageContainer,
on_delete=models.PROTECT,
related_name='items',
null=True)
quantity_change = models.IntegerField(default=0)
expiration_date = models.DateField(blank=True, null=True)
class PartAdmin(admin.ModelAdmin):
inlines = [StockPartInlines]
class StockPartInlines(admin.TabularInline):
model = Stock
fields = ['container', 'expiration_date', 'quantity']
readonly_fields = ['quantity']
def quantity(self, obj):
return obj._quantity
The equivalent SQL statement would be;
SELECT
part_id,
container_id,
expiration_date,
SUM(quantity_change)
FROM
inventory_stock
WHERE
part_id = part_id
GROUP BY
container_id,
expiration_date;
I have tried to get this to work by overriding the 'get_queryset()' method of StockPartInlines
def get_queryset(self, request):
qs = super(StockPartInlines, self).get_queryset(request)
.values('container','expiration_date')
.order_by('container')
.annotate(_quantity=Sum('quantity_change'))
return qs
This has been returning an error of 'dict' object has no attribute '_meta', traceback. I believe this is because using .values() in a queryset returns a queryset of dicts rather than a queryset of objects.
Is there a different approach I could take to display the results of this query as an inline form in the admin panel?

Django REST framework - filtering against query param with date Outside Views.py file

I created my "API" using REST framework, now trying to do filtering for it. That's how my models.py look for BookingStatement model.
class BookingStatement(BaseModel):
ticket_number = models.PositiveIntegerField(unique=True)
booking = models.OneToOneField(Booking, on_delete=models.PROTECT)
user_rate = AmountField()
agent_rate = AmountField()
total_amount = AmountField()
class Meta:
default_permissions = ()
def __str__(self):
return str(self.id)
Booking is One to One Key so the booking model has following Attributes.
class Booking(BaseModel):
bus_seat = models.ManyToManyField(Seat)
schedule = models.ForeignKey(Schedule, on_delete=models.PROTECT)
boarding_point = models.ForeignKey(
BoardingPoint,
on_delete=models.PROTECT,
null=True
)
remarks = models.JSONField(null=True, blank=True)
contact = PhoneNumberField(null=True)
booking_date_time = models.DateTimeField(auto_now_add=True)
class Meta:
default_permissions = ()
verbose_name = 'Booking'
verbose_name_plural = 'Bookings'
def __str__(self):
return '{}-{}'.format(self.user, self.customer_name)
I used generic ListAPIView in my views.py as following.
class BusCompanyTicketDetailView(generics.ListAPIView, BusCompanyMixin):
serializer_class = serializers.TicketDetailResponseSerializer
def get_queryset(self):
travel_date = (int(self.request.query_params.get('booking_date')))
print(booking_date)
return usecases.ListBusCompanyTicketUseCase(date=#'what should i pass?'#).execute()
I use usecases.py to filter booking_date_time with url as following.
http://127.0.0.1:8000/api/v1/ticket/list?booking_date=2021-1-29
So my usecase file to filter the Booking time is as following.
class ListBusCompanyTicketUseCase(BaseUseCase):
def __init__(self, date:datetime):
self._date = datetime
def execute(self):
self._factory()
return self._booking_statements
def _factory(self):
self._booking_statements = BookingStatement.objects.filter(booking__booking_date_time=?? need to get date from views.)
Problem is I don't know to how to get query params from url in my usecases to filter with booking date any help will be very helpful.
you should use django-filter to implement filtering on the viewsets. It's a bit of knowledge you have to build up, but once you understand it, you can do a lot of complex filtering logic with it. Trying to implement a filtering system yourself is always more difficult in the long run. For starting point, check out the official documentation: https://www.django-rest-framework.org/api-guide/filtering/. For the filter, check out the documentation on DRF integration: https://django-filter.readthedocs.io/en/stable/guide/rest_framework.html.

How can I display all fields from a Many-to-Many Model in Django Admin

I have the following Models:
class ModelA(models.Model):
some_field_A = models.CharField()
some_other_field_A = models.CharField()
class ModelB(models.Model):
some_field_B = models.CharField()
many_to_many_relation = models.ManyToManyField(ModelA)
In admin.py I am using filter_horizontal to edit the ManyToManyField:
class ModelB(admin.ModelAdmin):
model = ModelB
filter_horizontal = ('many_to_many_relation',)
but it shows only some_field_A and I want it to show both fields from ModelA, because the entries in ModelA are unique depending on both fields and as you can see from the picture there are multiple entries with the same value (i.e. some_field_A = EUV) but they have different values for some_other_field_A:
It displays the result of the __str__(…) method you defined in your ModelA, so if you return the value of some_field in the __str__(…) method, then it will return only the data of some_field.
You thus can alter this method and return both fields:
class ModelA(models.Model):
some_field_A = models.CharField()
some_other_field_A = models.CharField()
def __str__(self):
return f'{self.some_field_A} {self.some_other_field_A}'
I'm not sure if this exactly the solution you are looking for but you could override the __str__ method of ModelA to return the information in a single line.
So for example:
class ModelA(models.Model):
first_field = models.CharField(max_length=16)
second_field = models.CharField(max_length=16)
def __str__(self):
return f"{self.first_field} ({self.second_field'})"
Your admin view should then show each object as "foo (bar)"

access attribute via serializer django

I got following models:
class OrderItem(models.Model):
ordered_amount = models.IntegerField(validators=[MinValueValidator(0)])
amount = models.IntegerField(default=0)
order = models.ForeignKey(
Order, on_delete=models.CASCADE, related_name="order_items"
)
class Order(models.Model):
reference = models.CharField(max_length=50)
purchase_order = models.CharField(max_length=15, blank=True, null=True)
I'm now writing a serializer for listing orders. In this OrderSerializer I need to access amount and ordered_amount in the OrderItem class. How do I do this?
This is What I have now:
class AdminOrderListSerializer(serializers.ModelSerializer):
amount = serializers.IntegerField()
ordered_amount = serializers.IntegerField()
class Meta:
model = Order
fields = [
"purchase_order",
"reference",
"amount",
"ordered_amount",
]
# noinspection PyMethodMayBeStatic
def validate_amount(self, order):
if order.order_items.amount:
return order.order_items.amount
return
# noinspection PyMethodMayBeStatic
def validate_ordered_amount(self, order):
if order.order_items.ordered_amount:
return order.order_items.ordered_amount
return
This gives me following error:
AttributeError: Got AttributeError when attempting to get a value for field amount on serializer AdminOrderItemListSerializer.
The serializer field might be named incorrectly and not match any attribute or key on the Order instance.
Original exception text was: 'Order' object has no attribute 'amount'.
There are many ways to that, one of them is SerializerMethodField:
from django.db.models import Sum
class AdminOrderListSerializer(serializers.ModelSerializer):
amount = serializers.SerializerMethodField()
ordered_amount = serializers.SerializerMethodField()
def get_amount(self,obj):
return obj.order_items.aggregate(sum=Sum('amount'))['sum']
def get_ordered_amount(self,obj):
return obj.order_items.aggregate(sum=Sum('order_amount'))['sum']
Optimized solution
Another way of achieving this is to annotate the data to queryset, and access them in serializer. For that, you need to change in view:
class SomeView(ListAPIView):
queryset = Order.objects.annotate(amount=Sum('order_items__amount'),order_amount=Sum('order_items__order_amount'))
This is a optimized solution because it reduces database hits(it only hits once).

Categories