django admin - how to implement a custom field sort - python

Let's say I have a data model like so:
Apple->Fruit->Organic
so Apple has a foreign key to Fruit...
and Organic has 3 fields: name, title, level.
I'm displaying the 3 fields in a single column as name_title_level.
I want to be able to sort the result by clicking on the table header.
I've looked at:
https://djangosnippets.org/snippets/2110/
and tried it:
class SpecialOrderingChangeList(ChangeList):
def apply_special_ordering(self, queryset):
order_type, order_by = [self.params.get(param, None) for param in ('ot', 'o')]
special_ordering = self.model_admin.special_ordering
if special_ordering and order_type and order_by:
try:
order_field = self.list_display[int(order_by)]
ordering = special_ordering[order_field]
if order_type == 'desc':
ordering = ['-' + field for field in ordering]
queryset = queryset.order_by(*ordering)
except IndexError:
return queryset
except KeyError:
return queryset
return queryset
def get_query_set(self):
queryset = super(SpecialOrderingChangeList, self).get_query_set()
queryset = self.apply_special_ordering(queryset)
return queryset
#admin.register(Apple)
class AppleAdmin(admin.ModelAdmin):
list_display = ('x', 'get_name')
def get_name(self, obj):
return "{}_{}_{}".format(obj.fruit.organic.name,\
obj.fruit.organic.title, obj.fruit.organic.level)
special_ordering = {'name': ('fruit__organic__name', 'fruit__organic__title', 'fruit__organic__level')}
def get_changelist(self, request, **kwargs):
return SpecialOrderingChangeList
I'm not getting any error and the sort feature is not doing anything.
The get_query_set method is not being called.
Does anyone know how to do this?
Updates:
Here is the updated code. Now the method gets called but still there is no sorting functionality. I mean there is no link in the header at all.
models.py:
from django.db import models
class Organic(models.Model):
name = models.CharField(max_length=30)
title = models.CharField(max_length=30)
label = models.CharField(max_length=30)
def __unicode__(self):
return self.name
class Fruit(models.Model):
organic = models.ForeignKey(Organic)
def __unicode__(self):
return self.organic.name
class Apple(models.Model):
fruit = models.ForeignKey(Fruit)
color = models.CharField(max_length=30)
def __unicode__(self):
return self.color
admin.py:
from django.contrib import admin
from .models import *
from django.contrib.admin.views.main import ChangeList
#admin.register(Organic)
class OrganicAdmin(admin.ModelAdmin):
pass
#admin.register(Fruit)
class FruitAdmin(admin.ModelAdmin):
pass
class SpecialOrderingChangeList(ChangeList):
def apply_special_ordering(self, queryset):
order_type, order_by = [self.params.get(param, None) for param in ('ot', 'o')]
special_ordering = self.model_admin.special_ordering
if special_ordering and order_type and order_by:
try:
order_field = self.list_display[int(order_by)]
ordering = special_ordering[order_field]
if order_type == 'desc':
ordering = ['-' + field for field in ordering]
queryset = queryset.order_by(*ordering)
except IndexError:
return queryset
except KeyError:
return queryset
return queryset
def get_queryset(self, request):
queryset = super(SpecialOrderingChangeList, self).get_queryset(request)
queryset = self.apply_special_ordering(queryset)
return queryset
#admin.register(Apple)
class AppleAdmin(admin.ModelAdmin):
list_display = ('color', 'get_name')
def get_name(self, obj):
return "{}_{}_{}".format(obj.fruit.organic.name,\
obj.fruit.organic.title, obj.fruit.organic.label)
special_ordering = {'name': ('fruit__organic__name', 'fruit__organic__title', 'fruit__organic__label')}
def get_changelist(self, request, **kwargs):
return SpecialOrderingChangeList

it should be
get_queryset()
not
get_query_set()

Related

How to pass pk argument within class based view to queryset in Django

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.

Django REST, How to display every 5 element in a response?

Now the serializer displays all the data from the CoinCosts model in response (price,timestamp), but only every 5 element is needed, how to do it? Thanks
I need something like Entry.objects.all()[::5], but I don’t know how to do this with my code.
My code now:
serializers.py
class CoinCostsSerializer(serializers.ModelSerializer):
class Meta:
fields = ('price', 'timestamp')
model = CoinCosts
class CoinSerializer(serializers.ModelSerializer):
class Meta:
fields = ('symbol', 'crr', 'costs')
model = Coins
costs = CoinCostsSerializer(source='filtered_coincosts', many=True)
views.py
class DateTimeGteFilter(filters.IsoDateTimeFilter):
def filter(self, qs, value):
if value != None:
return qs.prefetch_related(Prefetch('coincosts_set', to_attr='filtered_coincosts', queryset=CoinCosts.objects.filter(timestamp__gte=value)
)
)
else:
return qs
class CoinCostFilterSet(filters.FilterSet):
timestamp = DateTimeGteFilter()
class Meta:
model = Coins
fields = {
'symbol': ['exact'],
}
You can use try and except like this in views.py in your def:
try:
obj = Entry.objects.all()[::5]
except IndexError:
obj = None

Django, searching with custom QuerySet, erroneously returning empty object

forgive me if this has been answered before, I have searched Stackover flow and I haven't seen any questions/answers that my problem
If you know one that matches please point me there
I have a Django project set up (version 1.11)
Along with Python 3.5.3
I have an app called Product and I wish to search its database
its model.py looks like this:
import random
import os
from django.db import models
from django.db.models import Q
from django.db.models.signals import pre_save
from django.core.urlresolvers import reverse
from .utils import unique_slug_generator
def get_filename_ext(filepath):
base_name = os.path.basename(filepath)
name, ext = os.path.splitext(base_name)
return name, ext
def upload_image_path(instance, filename):
new_filename = random.randint(1, 1000000)
name, ext = get_filename_ext(filename)
final_filename = '{new_filename}{ext}'.format(new_filename=new_filename, ext=ext)
return "products/{new_filename}/{final_filename}".format(
new_filename=new_filename,
final_filename=final_filename
)
class ProductQuerySet(models.query.QuerySet):
def active(self):
return self.filter(featured=True, active=True)
def featured(self):
return self.filter(featured=True)
def search(self, query):
lookups = (Q(title__icontains=query) |
Q(description__icontains=query)|
Q(price__icontains=query))
result = self.filter(lookups).distinct()
print(result)
return self.filter(lookups).distinct()
class ProductManager(models.Manager):
def get_queryset(self):
return ProductQuerySet(self.model, using=self._db)
def all(self):
return self.get_queryset()
def featured(self):
return self.get_queryset().featured()
def get_by_id(self, id):
qs = self.get_queryset().filter(id=id) # Product.object
if qs.count() ==1:
return qs.first()
return None
def search(self, query):
return self.get_queryset().active().search(query)
class Product(models.Model):
title = models.CharField(max_length=120)
slug = models.SlugField(blank=True, unique=True)
description = models.TextField()
price = models.DecimalField(decimal_places=2, max_digits=10, default=39.99)
image = models.ImageField(upload_to='products/', null=True, blank=True)
featured = models.BooleanField(default=False)
active = models.BooleanField(default=True)
timestamp = models.DateTimeField(auto_now_add=True)
objects = ProductManager()
def get_absolute_url(self):
return reverse("products:detail", kwargs={"slug": self.slug})
def __str__(self):
return self.title
def product_pre_save_receiver(sender, instance, *args, **kwargs):
if not instance.slug:
instance.slug = unique_slug_generator(instance)
pre_save.connect(product_pre_save_receiver, sender=Product)
I have a search app which facilitates this search.
its view.py looks like this:
from django.db.models import Q
from django.shortcuts import render
from django.views.generic import ListView
from products.models import Product
class SearchProductView(ListView):
template_name="search/view.html"
def get_context_data(self, *args, **kwargs):
context = super(SearchProductView, self).get_context_data(*args, **kwargs)
query = self.request.GET.get('q')
context['query'] = query
# SearchQuery.objects.create(query=query)
return context
def get_queryset(self, *args, **kwargs):
request = self.request
method_dict = request.GET
query = method_dict.get('q', None) # method_dict['q']
if query is not None:
return Product.objects.search(query)
return Product.objects.featured()
the situation is that in when I pass the value "Hat" to - def get_queryset() in Views.py
I receive an empty object. I know there is an entry in the data base called "hat"
it is only when I move the following code from ProductQuerySet:
lookups = (Q(title__icontains=query) |
Q(description__icontains=query)|
Q(price__icontains=query))
result = self.filter(lookups).distinct()
to replace
return Product.objects.search(query)
that i get a result.
Any ideas as to why the code at the top is returning an empty object?
The active method should only filter by active being true and not on features being true
class ProductQuerySet(models.query.QuerySet):
def active(self):
return self.filter(active=True)

Multiple annotate with sum and display data in admin - Django

I'm new to both Django and Python. Currently I'm trying the Django Admin by doing.
I've three models for a Django app, which are GoodsItem, SoldGoodsItem and FinishedGoodsItem. The models.py is:
from django.db import models
class GoodsItem(models.Model):
name = models.CharField(max_length=255)
size = models.DecimalField(max_digits=4, decimal_places=2)
INCHES = 'IN'
NUMBER = 'NUM'
GOODS_ITEM_SIZE_UNITS = (
(INCHES, 'Inches'),
(NUMBER, '#'),
)
size_unit = models.CharField(
max_length=4,
choices=GOODS_ITEM_SIZE_UNITS,
default=INCHES,
)
def __str__(self):
if(self.size_unit == self.NUMBER):
return "%s #%s" % (self.name, (self.size).normalize())
else:
return "%s %s\"" % (self.name, (self.size).normalize())
class FinishedGoodsItem(models.Model):
date = models.DateField()
goods_item = models.ForeignKey(GoodsItem, on_delete=models.CASCADE, related_name="finished_name")
weight = models.DecimalField(max_digits=6, decimal_places=3)
def __str__(self):
return str(self.goods_item)
class SoldGoodsItem(models.Model):
goods_item = models.ForeignKey(GoodsItem, on_delete=models.CASCADE, related_name="sold_name")
date = models.DateField()
weight = models.DecimalField(max_digits=6, decimal_places=3)
def __str__(self):
return str(self.goods_item)
And here is admin.py:
from django.contrib import admin
from django.db.models import Sum
from .models import GoodsItem, FinishedGoodsItem, SoldGoodsItem
#admin.register(SoldGoodsItem)
#admin.register(FinishedGoodsItem)
class FinishedGoodsItemAdmin(admin.ModelAdmin):
fields = ('date', 'goods_item', 'weight')
list_display = ('date', 'goods_item', 'weight')
#admin.register(GoodsItem)
class GoodsItemAdmin(admin.ModelAdmin):
list_display = ('__str__', 'finished_good', 'sold_good', 'stock_available')
def get_queryset(self, request):
qs = super(GoodsItemAdmin, self).get_queryset(request)
qs = qs.annotate(
finished_good=Sum('finished_name__weight'),
sold_good=Sum('sold_name__weight'),
stock_available=Sum('finished_name__weight') - Sum('sold_name__weight'),
)
return qs
def finished_good(self, obj):
return obj.finished_good
def sold_good(self, obj):
return obj.sold_good
def stock_available(self, obj):
return obj.stock_available
In stock_available for each GoodsItem, I want to display the difference between all entries of FinishedGoodsItem and all entries of SoldGoodsItem. For now, I'm getting incorrect value for all three annotated fields which are finished_good, sold_good and stock_available. I'm unable to find the reason for that. In Django Debug Toolbar suggest that duplicate queries are being executed.
This is known issue and occurs when we try to combine multiple aggregation, as mentioned in docs.
As a workaround for this particular problem, we can use Subquery expression. Here is my updated admin.py using Subquery expression in get_queryset method of GoodsItemAdmin.
from django.contrib import admin
from django.db.models import Subquery, Sum, OuterRef
from .models import GoodsItem, FinishedGoodsItem, SoldGoodsItem
#admin.register(SoldGoodsItem)
class SoldGoodsItemAdmin(admin.ModelAdmin):
fields = ('date', 'goods_item', 'weight')
list_display = ('date', 'goods_item', 'weight')
#admin.register(FinishedGoodsItem)
class FinishedGoodsItemAdmin(admin.ModelAdmin):
fields = ('date', 'goods_item', 'weight')
list_display = ('date', 'goods_item', 'weight')
#admin.register(GoodsItem)
class GoodsItemAdmin(admin.ModelAdmin):
list_display = ('__str__', 'finished_good', 'sold_good', 'stock_available')
def get_queryset(self, request):
qs = super(GoodsItemAdmin, self).get_queryset(request)
qs = qs.annotate(
finished_good = Subquery(FinishedGoodsItem.objects.filter(goods_item=OuterRef('pk'))\
.values('goods_item_id').annotate(sum=Sum('weight')).values('sum')[:1]),
sold_good = Subquery(SoldGoodsItem.objects.filter(goods_item=OuterRef('pk'))\
.values('goods_item_id').annotate(sum=Sum('weight')).values('sum')[:1])
)
return qs
def finished_good(self, obj):
return obj.finished_good
def sold_good(self, obj):
return obj.sold_good
def stock_available(self, obj):
finished_good = 0 if self.finished_good(obj) is None else self.finished_good(obj)
sold_good = 0 if self.sold_good(obj) is None else self.sold_good(obj)
return '-' if (finished_good == 0 and sold_good == 0) else finished_good - sold_good
Hope someone finds this useful.

Validation on query_params in Django Rest Framework

I want to return model_info only if query_params is given otherwise it should give some error message.
I tried below code but it is giving me keyerror when name is not passed in query params .
from rest_framework.validators import ValidationError
class ModelSerializer(serializers.ModelSerializer):
class Meta:
model = ModelName
fields = ('name', 'abbreviation')
def validate_name(self, value):
if value:
return value
else:
raise ValidationError('Enter name')
class ModelNameListList(generics.ListCreateAPIView):
renderer_classes = (JSONRenderer, )
serializer_class = ModelSerializer
def get_queryset(self):
queryset = ModelName.objects.all()
name = self.request.query_params['name']
queryset = queryset.filter(Q(name__icontains=name) | Q(abbreviation__icontains=name)).all()
return queryset
I cannot use get method because I am also using pagination, If I will use get method it will return me all the results.
When I am using below code in get_queryset I am getting response object has no length
def get_queryset(self):
queryset = ModelName.objects.all()
name = self.request.query_params.get('name', None)
if name:
queryset = queryset.filter(Q(name__icontains=name) | Q(abbreviation__icontains=name)).all()
else:
content = {'errors': 'name is missing'}
return Response(content)
return queryset
With this function:
def get_queryset(self):
queryset = ModelName.objects.all()
name = self.request.query_params.get('name', None)
if name:
queryset = queryset.filter(Q(name__icontains=name) | Q(abbreviation__icontains=name)).all()
else:
raise exceptions.ParseError("name not supplied")
return queryset
You should make sure you are always returning a queryset (or raising an exception if that is how you want to handle it).

Categories