Django-Tables2 add extra columns from dictionary - python

I apologize if this question has been asked before but I couldn't find my specific use case answered.
I have a table that displays basic product information. Product details such as price, number of sales, and number of sellers are scraped periodically and stored in a separate database table. Now I want to display both the basic product information and scraped details in one table on the frontend using tables2. To do this, I wrote a function in my Product model to fetch the latest details and return them as a dictionary this way I can use a single Accessor call.
# models.py
class Product(models.Model):
created_at = models.DateTimeField(auto_now_add=True)
name = models.CharField(max_length=256)
brand = models.ForeignKey(Brand)
category = models.CharField(max_length=128, choices=CATEGORY_CHOICES)
def __unicode__(self):
return self.name
def currentState(self):
currentDetailState = ProductDetailsState.objects.filter(
product=self
).latest('created_at')
# return current details as a dictionary
return {
price: currentDetailState.price,
num_sellers: currentDetailState.num_sellers,
num_sales: currentDetailState.num_sales
}
class ProductDetailsState(models.Model):
product = models.ForeignKey(Product)
created_at = models.DateTimeField(auto_now_add=True)
price = models.DecimalField(max_digits=6, decimal_places=2, null=True)
num_sellers = models.IntegerField(null=True)
num_sales = models.IntegerField(null=True)
def __unicode__(self):
return self.created_at
# tables.py
class ProductTable(tables.Table):
productBrand = tables.Column(
accessor=Accessor('brand.name'),
verbose_name='Brand'
)
currentRank = tables.Column(
accessor=Accessor('currentRank')
)
class Meta:
model = Product
...
How do I now use this returned dictionary and split it into columns in my Product table? Is there another way to use an Accessor than how I am doing it?

You can use the Accessor to traverse into the dict, so something like this should work:
class ProductTable(tables.Table):
# brand is the name of the model field, if you use that as the column name,
# and you have the __unicode__ you have now, the __unicode__ will get called,
# so you can get away with jus this:
brand = tables.Column(verbose_name='Brand')
currentRank = tables.Column()
# ordering on the value of a dict key is not possible, so better to disable it.
price = tables.Column(accessor=tables.A('currentState.price'), orderable=False)
num_sellers = tables.Column(accessor=tables.A('currentState.num_sellers'), orderable=False)
num_sales = tables.Column(accessor=tables.A('currentState.num_sales'), orderable=False)
class Meta:
model = Product
While this works, sorting is also nice to have. In order to do that, your 'currentState' method is a bit in the way, you should change the QuerySet you pass to the table. This view shows how that could work:
from django.db.models import F, Max
from django.shortcuts import render
from django_tables2 import RequestConfig
from .models import Product, ProductDetailsState
from .tables import ProductTable
def table(request):
# first, we make a list of the post recent ProductDetailState instances
# for each Product.
# This assumes the id's increase with the values of created_at,
# which probably is a fair assumption in most cases.
# If not, this query should be altered a bit.
current_state_ids = Product.objects.annotate(current_id=Max('productdetailsstate__id')) \
.values_list('current_id', flat=True)
data = Product.objects.filter(productdetailsstate__pk__in=current_state_ids)
# add annotations to make the table definition cleaner.
data = data.annotate(
price=F('productdetailsstate__price'),
num_sellers=F('productdetailsstate__num_sellers'),
num_sales=F('productdetailsstate__num_sales')
)
table = ProductTable(data)
RequestConfig(request).configure(table)
return render(request, 'table.html', {'table': table})
This simplifies the table definition, using the annotations created above:
class ProductTable(tables.Table):
brand = tables.Column(verbose_name='Brand')
currentRank = tables.Column()
price = tables.Column()
num_sellers = tables.Column()
num_sales = tables.Column()
class Meta:
model = Product
You can find the complete working django project at github

Related

how to pivot with django pandas using foreign keys

I am using django-panda to make a dynamic table and I have achieved it, the problem I have is that I would like to add more rows to the table using foreign key relationships, in the documentation it says: "to span a relationship, just use the field name of related fields in models, separated by double underscores, "however this does not work with to_pivot_table. I did a test with to_dataframe and here the logic if it gives results, but what I need is to implement it in the pivot
these are my models:
class Student(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE, primary_key=True,related_name='student')
quizzes = models.ManyToManyField(Quiz, through='TakenQuiz')
interests = models.ManyToManyField(Subject, related_name='interested_students')
def get_unanswered_questions(self, quiz):
answered_questions = self.quiz_answers \
.filter(answer__question__quiz=quiz) \
.values_list('answer__question__pk', flat=True)
questions = quiz.questions.exclude(pk__in=answered_questions).order_by('text')
return questions
def __str__(self):
return self.user.username
class TakenQuiz(models.Model):
student = models.ForeignKey(Student, on_delete=models.CASCADE, related_name='taken_quizzes')
quiz = models.ForeignKey(Quiz, on_delete=models.CASCADE, related_name='taken_quizzes',verbose_name='Examen')
score = models.FloatField()
date = models.DateTimeField(auto_now_add=True)
objects = DataFrameManager()
and my views
class ResultadosTotales(ListView):
def get(self,request,*args,**kwargs):
examen=TakenQuiz.objects.all()
qs=TakenQuiz.objects.all()
df2=taken.to_dataframe(['student__user__first_name' ,'quiz', 'score'], index='student')
print(df) #here I did the test with the related attribute and if it works
rows = ['student','student__user__first_name'] #Here it does not work even though the documentation uses the same logic
rows = ['student']
cols = ['quiz']
print(df2)
pt = qs.to_pivot_table(values='score', rows=rows, cols=cols,fill_value="falta")
pt=pt.to_html(classes="table table-striped border")
context={ 'pivot': pt
}
return render(request, 'classroom/teachers/resultados_totales.html',context )
espero puedan ayudarme

Calculate a number in a Django model from another model's data

I want to take data (amount_spent) from the field of each user and add those numbers up and display them in another field (total_revenue) from a different model (RevenueInfo).
from __future__ import unicode_literals
from django.contrib.auth.models import User
from django.db import models
from django import forms, views
# Create your models here.
#LoginInfo is being used, LoginForms in forms.py is
class LoginInfo(models.Model):
username = models.CharField('', max_length=10)
password = models.CharField('', max_length=15)
class ExtendedProfile(models.Model):
user = models.OneToOneField(User)
amount_spent = models.DecimalField(max_digits=6, decimal_places=2)
class RevenueInfo(models.Model):
total_amount_spent = models.DecimalField(max_digits=6, decimal_places=2, default=0)
total_revenue = models.ForeignKey(ExtendedProfile, null=True)
class Product(models.Model):
category = models.CharField(max_length=100)
name = models.CharField(max_length=100)
description = models.TextField()
#photo = models.ImageField()
price_CAD = models.DecimalField(max_digits=6, decimal_places=2)
quantity = models.DecimalField(max_digits=2, decimal_places=0, null=True)
How could I go about this? Would I iterate of each Usermodel and find User.amount_spent then add that to RevenueInfo.total_revenue? I'm not sure how to put that into code. Also I'm pretty sure I don't need both total_amount_spent and total_revenue but I feel like I need a ForeignKey
You could add a classmethod to the ExtendedProfile model to aggregate the amount_spent value for each User (which bypasses the need for a separate RevenueInfo model):
from django.db.models import Sum
class ExtendedProfile(models.Model):
....
#classmethod
def total_user_spend(cls):
return cls.objects.aggregate(total=Sum('amount_spent'))
Then you can get the total spend using ExtendedProfile.total_user_spend():
>>> ExtendedProfile.total_user_spend()
{'total': Decimal('1234.00')}
Yes, you can write a method for that in your model. There are 2 ways for it.
1) Writing a method that calculates the values and sets it to a instance value.
2) Writing a method that calculates the value and directly returns it.
For example purpose, here is the code for 2nd type.
# models.py
def total_amount_spent(self):
temp_values = [int(user.amount_spent) for user in ExtendedProfile.objects.all()]
return sum(temp_values)
And for using that value in views , but remeber it would be an integer by default
#views.py
value = RevenueInfo.total_amount_spent()
Avoid iterating over database entities in python (it can get really slow). Look into aggregation, it allows you to efficiently get sum (average, max, min, etc...) of values in a database:
>>> from django.db.models import Sum
>>> ExtendedProfile.objects.all().aggregate(Sum('amount_spent'))
{'amount_spent__sum': Decimal('1234.56')}
>>> # ... do whatever you want with the return value

Django one-to-many relations in a template

I am searching of a method to obtain html forms from some one-to-many relations, like order-lineorder, invoice-lineinvoice, etc.
Let me an example:
# models.py
class Order(models.Model):
date = models.DateTimeField()
number = models.IntegerField(default=0)
class LineOrder(models.Model):
description = models.TextField()
price = models.FloatField()
order = models.ForeignKey(Order)
# views.py
def order_form(request):
form = OrderForm()
table_lineorder = LineOrderTable([])
RequestConfig(request).configure(table)
return render(request, "order_form.html", {"form": form, "table": table_lineorder})
Then, I want to obtain the order template with "generic attributes" (date, number), and a table list (originally empty) of lines order. Add some action like add, edit and remove must be possible.
I think that a solution like django-tables2 is possible, but I can't add rows dinamically, I think.
Thanks in advice.
[EDIT]
I have found the solution. It is django-dynamic-formset
I'm not quite clear about your question, but I guess this might be what you want:
class Order(models.Model):
date = models.DateTimeField()
number = models.IntegerField(default=0)
items = model.ManyToManyField(Item)
class Item(models.Model):
description = models.TextField()
price = models.FloatField()
It should be equivalent to one-to-many if you don't assign one Item to multiple Orders.

Django order by queryset from a method

I would like to order by entry_count but it seems (from the django error) I can only order by name and status. How can I order by entry_count in this example? Thanks
Category.objects.filter(status='Published').order_by('-entry_count')
Model.py
class Category(models.Model):
"""
Entry Categories.
"""
name = models.CharField(max_length=60, help_text="Title of Category")
status = models.CharField(max_length=16, choices=STATUS_CHOICES,
default="Draft", )
#property
def entry_count(self):
return Entry.objects.filter(category=self.id).count()
You can do this by using aggregation:
from django.db.models import Count
Category.objects.annotate(
entry_count=Count('entry_set')
).order_by('-entry_count')
Like this, you'll get all the counts in one query and all the category objects will have entry_count, so you can remove the #property from the model.

Django retrieve all related attributes for model

I am writing a store based app. Say theres Store A , Store A can have multiple users, and each user can belong to multiple stores. However, each store has Products and each Product has sizes associated with them.
class Variation(models.Model):
product = models.ForeignKey("Product")
size = models.CharField(choices=SIZES, max_length=5)
pid = models.CharField(unique=True, max_length=100, verbose_name="Product ID")
id_type = models.CharField(max_length=5, choices=ID_TYPES, default="UPC")
price = models.DecimalField(max_digits=5, decimal_places=2)
store = models.ForeignKey("Store")
class Meta:
unique_together = ("store", "pid")
class Product(models.Model):
item = models.CharField(max_length=100, verbose_name="Parent SKU", help_text="reference# - Color Code")
# Google docs sp key
store = models.ForeignKey(Store)
class Store(models.Model):
name = models.CharField(max_length=100)
store_id = models.PositiveIntegerField(unique=True)
admins = models.ManyToManyField(User)
objects = StoreManager()
def __unicode__(self):
return self.name
so i had to write a custom manager for Product in order to filter all products by store, and override the queryset method for this model's admin class, and do so for EVERY attribute belonging to said store. So basically my question is, is there a way to filter all attributes related to a store per store, ex products, tickets, variations
EDIT
This is the product manager so far
class ProductManager(models.Manager):
def active(self, **kwargs):
return self.filter(in_queue=False, **kwargs)
def by_store(self, store=None, **kwargs):
return self.filter(store__id__exact=store, **kwargs)
def from_user(self, request):
qs = self.model.objects.none()
for store in request.user.store_set.all():
qs = qs | store.product_set.filter(in_queue=False)
return qs
so basically, in order to display the products in the change list page, i use the from user method, which returns all the products available to the logged in user
it sounds like what you want to be using is the the django "Sites" framework
add a ForeignKey to Store to point to Site and make it unique.
it may be wise to point Variation and Products' ForeignKeys at Site instead of Store at this point too so that in your views you can filter your results by current site.

Categories

Resources