How to count the amount of objects in a django joined table? - python

My problem is simple: I have Users who own Assets or Assets which belong to Users If you prefer and I cannot make it to retrieve the number (count) of Assets each User has. I know this might be sound silly to most of you but I am new to python/django (coming from PHP/MySQL) and I do not know how things work here. I do not want to be engaged with raw SQL - this would be my last choice If nothing else works.
(*) I have removed all non-related raws from the code
Users
class Users(models.Model):
firstname = models.CharField(max_length=100)
lastname = models.CharField(max_length=100)
Assets
class Assets(models.Model):
serial = models.CharField(unique=True, max_length=100)
user = models.ForeignKey('Users', blank=True, null=True)
# this is what I am playing with to retrieve the number of assets each user owns
#classmethod
def user_assets(self):
return Assets.objects.filter(user=user).count()
views.py
class UserList(ListView):
model = Users
def get_context_data(self, **kwargs):
context = super(UserList, self).get_context_data(**kwargs)
context['user_assets'] = self.model.user_assets()
return context
template
{% for user in object_list %}
<tr>
<td>{{ user.id }}</td>
<td>
{{ user_assets }}
</td>
</tr>
{% endfor %}
How can I get that number? I have read about aggregations, annotations and filters but can't really get it.
EDIT:
I am looking for a simple solution by using class based views and easily expandable (I may want to add other models later)

In your UserList instead using model, use this queryset:
from django.db.models import Count
class UserList(ListView):
queryset = Users.objects.annotate(num_assets=Count('assets'))
and define your user field like so:
user = models.ForeignKey('Users', blank=True, null=True, related_name='assets')
then from template:
{{ user.num_assets }}
Also please remember, it's a good practice to use singular model names, to avoid confusion with reverse relation names..

You are doing weird things. Use the related managers that django give you instead. I'll write the view as a function based view:
views.py
def users_list(request):
object_list = Users.objects.all()
render(request, 'mytemplate.html', { 'object_list': object_list })
You can get the counts directly in the template via the RelatedManager:
mytemplate.html
{% for user in object_list %}
<tr>
<td>{{ user.id }}</td>
<td>
{{ user.assets_set.count }}
</td>
</tr>
{% endfor %}
You could also annotate with a count. But learn to float before you swim :)
BTW, you should call your models "User" and "Asset", not Users and Assets.

You need to use select_related(), count() and pass user instance as argument to class method like so:
#classmethod
def user_assets(cls,user):
return Assets.objects.select_related('Users').filter(user=user).count()
and then use it like so:
user = Users.objects.all()[0] # some user object (this assumes you have at least one user)
Assets.user_assets(user)
this should work fine, you can try it in the shell.
In your context this will be used like this:
user = self.model.all()[0] # or get or filter just get some particular user
context['user_assets'] = Assets.user_assets(user)
EDIT: added links, and Users.object.all() instead of Users.object.get(), also added example suited to your specific use case.

Related

Saving class-based view formset items with a new "virtual" column

I have a table inside a form, generated by a formset.
In this case, my problem is to save all the items after one of them is modified, adding a new "virtual" column as the sum of other two (that is only generated when displaying the table, not saved).
I tried different ways, but no one is working.
Issues:
This save is not working at all. It worked when it was only one form, but not for the formset
I tried to generate the column amount as a Sum of box_one and box_two without success. I tried generating the form this way too, but this is not working:
formset = modelformset_factory(
Item, form=ItemForm)(queryset=Item.objects.order_by(
'code__name').annotate(amount=Sum('box_one') + Sum('box_two')))
This issue is related to this previous one, but this new one is simpler:
Pre-populate HTML form table from database using Django
Previous related issues at StackOverflow are very old and not working for me.
I'm using Django 2.0.2
Any help would be appreciated. Thanks in advance.
Current code:
models.py
class Code(models.Model):
name = models.CharField(max_length=6)
description = models.CharField(max_length=100)
def __str__(self):
return self.name
class Item(models.Model):
code = models.ForeignKey(Code, on_delete=models.DO_NOTHING)
box_one = models.IntegerField(default=0)
box_two = models.IntegerField(default=0)
class Meta:
ordering = ["code"]
views.py
class ItemForm(ModelForm):
description = CharField()
class Meta:
model = Item
fields = ['code', 'box_one', 'box_two']
def save(self, commit=True):
item = super(ItemForm, self).save(commit=commit)
item.box_one = self.cleaned_data['box_one']
item.box_two = self.cleaned_data['box_two']
item.code.save()
def get_initial_for_field(self, field, field_name):
if field_name == 'description' and hasattr(self.instance, 'code'):
return self.instance.code.description
else:
return super(ItemForm, self).get_initial_for_field(
field, field_name)
class ItemListView(ListView):
model = Item
def get_context_data(self, **kwargs):
data = super(ItemListView, self).get_context_data()
formset = modelformset_factory(Item, form=ItemForm)()
data['formset'] = formset
return data
urls.py
app_name = 'inventory'
urlpatterns = [
path('', views.ItemListView.as_view(), name='index'),
item_list.html
...
<div>
<form action="" method="post"></form>
<table>
{% csrf_token %}
{{ formset.management_form }}
{% for form in formset %}
<thead>
<tr>
{% if forloop.first %}
<th>{{ form.code.label_tag }} </th>
<th>{{ form.description.label_tag }} </th>
<th> <label>Amount:</label> </th>
<th>{{ form.box_one.label_tag }} </th>
<th>{{ form.box_two.label_tag }} </th>
{% endif %}
</tr>
</thead>
<tbody>
<tr>
<td>{{ form.code }}</td>
<td>{{ form.description }}</td>
<td>{{ form.amount }}</td>
<td>{{ form.box_one }}</td>
<td>{{ form.box_two }}</td>
</tr>
</tbody>
{% endfor %}
<input type="submit" value="Update" />
</table>
</form>
</div>
...
Annotating query with virtual column
Sum is an aggregate expression and is not how you want to be annotating this query in this case. Instead, you should use an F exrepssion to add the value of two numeric fields
qs.annotate(virtual_col=F('field_one') + F('field_two'))
So your corrected queryset would be
Item.objects.order_by('code__name').annotate(amount=F('box_one') + F('box_two'))
The answer provided by cezar works great if intend to use the property only for 'row-level' operations. However, if you intend to make a query based on amount, you need to annotate the query.
Saving the formset
You have not provided a post method in your view class. You'll need to provide one yourself since you're not inheriting from a generic view that provides one for you. See the docs on Handling forms with class-based views. You should also consider inheriting from a generic view that handles forms. For example ListView does not implement a post method, but FormView does.
Note that your template is also not rendering form errors. Since you're rendering the formset manually, you should consider adding the field errors (e.g. {{ form.field.errors}}) so problems with validation will be presented in the HTML. See the docs on rendering fields manually.
Additionally, you can log/print the errors in your post method. For example:
def post(self, request, *args, **kwargs):
formset = MyFormSet(request.POST)
if formset.is_valid():
formset.save()
return SomeResponse
else:
print(formset.errors)
return super().post(request, *args, **kwargs)
Then if the form does not validate you should see the errors in your console/logs.
You're already on the right path. So you say you need a virtual column. You could define a virtual property in your model class, which won't be stored in the database table, nevertheless it will be accessible as any other property of the model class.
This is the code you should add to your model class Item:
class Item(models.Model):
# existing code
#property
def amount(self):
return self.box_one + self.box_one
Now you could do something like:
item = Item.objects.get(pk=1)
print(item.box_one) # return for example 1
print(item.box_two) # return for example 2
print(item.amount) # it will return 3 (1 + 2 = 3)
EDIT:
Through the ModelForm we have access to the model instance and thus to all of its properties. When rendering a model form in a template we can access the properties like this:
{{ form.instance.amount }}
The idea behind the virtual property amount is to place the business logic in the model and follow the approach fat models - thin controllers. The amount as sum of box_one and box_two can be thus reused in different places without code duplication.

Filter on variable of ManyToMany-field inside Django Template

I have a job-object and a content-object.
A job object gets created when a user wants to retrieve a set of content-objects in exchange for credits.
models.py:
class JobId(models.Model):
user = models.ForeignKey(User, blank=True, null=True, default=None)
job_time = models.DateTimeField(auto_now_add=True)
class content(models.Model):
job_id_list = models.ManyToManyField(JobId , related_name='JobId', blank=True)
job_id_list_not_provided = models.ManyToManyField(JobId , related_name='JobIdFailed', blank=True)
free_content = models.CharField(max_length=500, null=True, blank=True)
paying_content = models.CharField(max_length=500, null=True, blank=True)
For all content-objects part of the job, the JobId-object is added to the job_id_list - not keeping credit levels in mind. Different user can all run multiple jobs on the content objects.
For too-big jobs exceeding the credit amount of the user, the content-objects that would push the credit level below zero, get also the JobID-object added to the job_id_list_not_provided field of the content-object.
For a a specific user, we can retrieve the two sub-sets of found and not-found content-objects with following queries:
views.py:
found_results_list = results_list.filter(job_id_list_not_provided__user= None).distinct()
not_found_results_list = results_list.filter(job_id_list_not_provided__user=request.user).distinct()
My challenge:
Result lists are over 100-objects in size, so I would like to use pagination to get a nice view on my page
When not considering pagination, I could simply pass the 2 lists (found and not found) and loop over each list with a template from django:
Html:
<table>
<body>
{% for result in found_results_list %}
<tr>
<td>{{result.free_content}}</td>
<td>{{result.paying_content}}</td>
</tr>
{% empty %}
<tr>no result</tr>
{% endfor %}
{% for result in not_found_results_list %}
<tr>
<td>{{result.free_content}}</td>
<td>pay for the content</td>
</tr>
{% empty %}
<tr>no result</tr>
{% endfor %}
</tbody>
</table>
But what to do if I want to use pagination? It seems you can only use one result-list.
views.py
(I used .distinct() as sometimes the objects have too much jobs added to it from the same user)
results_list = xlinkdatabase_validated.objects.filter(job_id_list__user=request.user).distinct()
Main problem is:
I don't know to check inside the template if the paying_content can be visible if is start from an overall result_list both including found and not_found objects.
I tried using {{result.job_id_list_not_provided}} template inside Html, but this returns all job-objects of the content-object, even if these are not related to the specific user, which is logic of course.
How would I tackle this problem?
Thanks
I eventually solved my own issue by building a custom template tag that I unleash on my content-objects inside my view.
extra tags.py:
from django import template
from frontendapp.models import *
register = template.Library()
#register.filter
def has_viewing_rights(result, user):
has_viewing_rights = True
job_id_list_not_providedd = result.job_id_list_not_provided.all()
for item in job_id_list_not_providedd:
if item.user == user:
has_viewing_rights = False
return has_viewing_rights
html:
{% if result|has_viewing_rights:request.user%}
provided
{%else%}
not provided
{%endif%}

Get the value in the intermediate model in a Through Relationship

As I loop through a Prize.objects.all() set, I am trying to get the quantity value from PrizeItem, but it seems to reference only the GameItem model, not giving access to the intermediate PrizeItem attributes.
How do I get access to the Through model's attributes?
MODELS:
class GameItem(models.Model):
'...'
class Prize(models.Model):
'...'
item = models.ManyToManyField(GameItem, through='PrizeItem')
class PrizeItem(models.Model):
#relations
game_item = models.ForeignKey(GameItem, on_delete=models.CASCADE)
prize = models.ForeignKey(Prize, on_delete=models.CASCADE)
#Item details
quantity = models.IntegerField(null=True, blank=True, default=1)
VIEWS:
def gameprizes(request):
prizes=Prize.objects.all()
context={'prizes':prizes}
return render(request, "the-app/game-prizes.html", context)
TEMPLATE:
{% if prize.item.all %}
<table class="table table-condensed">
<tbody>
{% for prize_item in prize.item.all %}
<tr>
<td>{{ prize_item.type }}</td><td>{{ prize_item.name }} {{ prize_item.quantity }}</td>
</tr>
{% endfor %}
</tbody>
</table>
{% endif %}
It gets a little ugly when it comes to intermediate models in querysets. One way you can address this would be:
class Prize(models.Model):
'...'
item = models.ManyToManyField(GameItem, through='PrizeItem')
def quantity(self):
return self.item.quantity
Note that this does lead to additional queries, and you can use prefetch_related in your query to optimize
Another option would be to use the template
For game item, you would be doing something like:
class GameItem(models.Model):
'...'
def quantity(self):
return self.prizeitem_set.first()
What the hell with the models?
~> Use the #karthikr answer
Just prepare in views.py (overwrite query_set)
Use (_set ~ join) in html
Have a nice day, don't make it hard, give the hardest part for the strongest (server - database)

Django page's filter for a ListView

I'm developing a Django App for a car rental customer.
In my model I have a Car model with their car properties (ex: passengers, ports, transmission, category, ecc...).
I had correctly setup my view and template that list all cars.
Now, I need to implement a form for filters the cars list by some characteristics.
I would like to implement in my CarListView a filter logic that filter cars by characteristics included in the query string.
ex: /cars/?ports=1&passengers=3
How can I implements this in an elegant Django way?
Is better to use a form with GET method or setup custom urls in my urls.py??
One possible solution (for other users):
class CarResultsView(BreadcrumbsMixin, ListView):
queryset = Vehicle.objects.all()
template_name = "my_app/cars.html"
def get_queryset(self):
queryset = super(CarResultsView, self).get_queryset()
search_form = VehicleFilterForm(self.request.GET)
if search_form.is_valid():
queryset = queryset.filter(**search_form.cleaned_data)
return queryset
def get_context_data(self, **kwargs):
context = super(CarResultsView, self).get_context_data(**kwargs)
get_params = self.request.GET
context['form'] = VehicleFilterForm(get_params)
return context
In your opinion, could exists one more elegant solution?
This is right in the docs.
You're going to want to override the get_queryset() method, as well as modify your URL patterns to reflect the kwargs you're going to be capturing.
You must define the correct regexp in your "urls.py" to capture the car parameters. See for example this question.
Knowing the parameters, you will be able to filter the list of all cars by the needed characteristics in your view function. Something like this:
cars = Car.objects.filter(ports = ports, passengers = passengers)
Be sure to check for empty/undefined car parameters in your view. So that if the request doesn't have any parameters, all cars are displayed.
Forms
If you don't have previous experience with forms, read and try Working with forms in Django.
In your car list template, include both the search form and the filtered list. Similar to this:
<form action="/cars" method="post">
{% csrf_token %}
{{ form }}
<input type="submit" value="Search" />
</form>
<table>
{% for car in cars %}
<tr><td>{{ car.name }}</td>
<td>{{ car.engine }}</td>
<td>{{ car.price }}</td>
</tr>
{% endfor %}
</table>
When the form is submitted, a POST request is issued to your site. The GET request must show an empty form ("unbound form") and list of all cars. The template will be used to generate response both for POST and GET requests.

Sorting by date with a DB in Django

I have the following model.py.
from django.db import models
class Address(models.Model):
addr = models.CharField(max_length=150)
def __unicode__(self):
return u'%s' % (self.addr)
class Anniversary(models.Model):
date = models.DateField()
def __unicode__(self):
return u'%s' % (self.date)
class Person(models.Model):
name = models.CharField(max_length=50)
birthday = models.DateField()
anniversary = models.ForeignKey(Anniversary)
address = models.ForeignKey(Address)
def __unicode__(self):
return u'%s %s %s %s' % (self.name, self.birthday, self.anniversary, self.address)
I want to print the contents of all entries into my template. But sorted by date of birth against the current date. i.e most recent first and then name. What is the best way to do this. Should I sort it first and then append it to a list or dict ?
Any pointers would be great.
Thanks,
You can add default ordering in a model's meta class, e.g.
class Person(models.Model):
# fields
class Meta:
ordering = ('anniversary__date',)
then in your template it's as easy as:
<ul>
{% for person in persons %}
<li>{{ person.anniversary.date|date:"M d, Y" }} - {{ person.name }}</li>
{% endfor %}
</ul>
If you need custom ordering within a view (to filter out persons based on the request object):
def myview(request):
context = dict(
persons = Person.objects.exclude(person.id=request.user.id)\
.order_by('anniversary__date')
)
return render_to_response('app/template.html', context)
N.b. Adding a minus before your order_by parameter order_by('-attr') reverses ordering.
As #DanielEriksson mentioned, if your case isn't hypothetic, it seems you should simplify things.
I think you are looking for this :
persons = Person.objects.all().order_by(birthday ,anniversary)
In you view you can get the current date using :
import datetime
now = datetime.datetime.now()
and then :
persons = Person.objects.all().order_by(now, anniversary)
Hope it helps!
I personally like to avoid sorting in the Model, since it's more a presentation/view type thing. I tend to use sorted in render_to_response for single value sorting, or order_by for multi-value sorting. Using sorted in the return statement lets me kind of split the difference between the view/template, since Django views aren't entirely controllers. Anyway, this is just my preference. You can also use dictsort in the template to do single value sorting.
You have indicated you want to sort using multiple values, first by most recent birth date and second by the name. To do this, I would use order_by in a view, then comment my return statement:
from django.shortcuts import render_to_response
def view_persons(request):
persons = Person.objects.all().order_by('-birthday', 'name')
return render_to_response(
'view_persons.html',
{
'persons': persons, # ordered by most recent birthday, then name
},
context_instance=RequestContext(request)
)
In the template, you have little more to do than:
{% if persons %}
<table>
<thead>
<tr>
<th>Birthday</th>
<th>Name</th>
</tr>
</thead>
<tbody>
{% for person in persons %}
<tr>
<td>{{ person.birthday }}</td>
<td>{{ person.name }}</td>
</tr>
{% endfor %}
</tbody>
</table>
{% else %}
<p>There are no people!</p>
{% endif %}
I guess the complexity is that you need to compare the sorted date against the current date, not simply sorted by date.
If you are using Postgres and are happy to stick to raw SQL, you could potentially use their datetime functions, such as age
http://www.postgresql.org/docs/8.1/static/functions-datetime.html
Otherwise, you would need to select where the date of birth/anniversary is greater than now. It might be simpler if you refactored your anniversary model to store the day, month and year a separate fields, so that you can ignore the year when filtering.
I recently posted some code in a question, that might give you some hints - I need to return events closest to now. See:
Is it bad practice to return a tuple from a Django Manager method rather than a queryset?

Categories