Trying to make a Django / Mezzanine "Page last updated by on date" - mysql

I'm trying to make a "page last updated by (username)" on (date)" line at the bottom of my mezzanine site page. I've successfully got the date part with the syntax:
{{ page.updated|date:"M d, Y" }}
but the username seems more complicated. I have a nice list in django_admin_log containing action_time, user_id and object_id and I can access & display object_id from within my template using {{ page.id }}.
The username is in auth_user and in days of old I would have put in some SQL with a nice table join on user_id to retrieve this information and display it.
I can't seem to get the syntax right - this kind of thing looked promising:
page.objects.raw("select id, title from page")
but no matter how I try to massage it into my base.html with {{ xxx }} or {% xxx %}, I always seem to get an error.
Thanks for any pointers

I've found a way to do this:
I created a custom tag, saved in /appname/templatetags/appname_tags.py, code as follows:
from django import template
from django.db import connection
register = template.Library()
#register.inclusion_tag(‘appname/_updated_by.html')
def updated_by(pageid):
cursor = connection.cursor()
cursor.execute('select user_id from django_admin_log where object_id = %s order by id desc limit 1', [pageid])
userid_set = cursor.fetchone()
userid = userid_set[0]
cursor.execute('select first_name, last_name from auth_user where id = %s', [userid])
username = cursor.fetchone()
return {'first_name': username[0],'last_name': username[1]}
This tag gets rendered via /appname/templates/appname/_updated_by.html, which simply contains:
{{ first_name }} {{ last_name }}
The whole thing then gets called from base.html via this code:
{% load appname_tags %}
And then add at the appropriate point:
Page Last Updated: {{ page.updated|date:"d/n/Y" }} by {% updated_by page.id %}
Hope this helps somebody, I'd be interested to hear if there's an easier / more elegant way to do this!

You should use the LogEntry model[1].
I would probably make a custom context processor[2] that get's the request's
Page, finds the newest LogEntry for the Page instance and adds updated_on
and updated_by context variables.
[1] https://github.com/django/django/blob/master/django/contrib/admin/models.py#L27
[2] https://docs.djangoproject.com/en/dev/ref/templates/api/#writing-your-own-context-processors

Related

Using variables in get_columns_in_relation dbt function

I'm fairly new to macros and variables in dbt so apologize if this is really straightforward but I'm attempting the below but it's not recognizing the {{customer_dataset}}.{{table_name}} part
{%- set table_name = 'orders' -%}
{%- set customer_dataset = customer.id -%}
{%- set columns = adapter.get_columns_in_relation(`{{customer_dataset}}.{{table_name}}`) -%}
How do I do this properly if I want to dynamically loop through a bunch of tables for the get_columns_in_relation function?
First off, don't nest your curlies.
Second, get_columns_in_relation expects a Relation, not a string. A Relation is the thing returned by {{ ref() }} or {{ source() }}, so it's easiest to get a Relation from a model name, not a database identifier. If you want to use a table in your database that isn't a dbt model, create a source for it first.
In short, your code should probably look like this:
{%- set columns = adapter.get_columns_in_relation(source(customer_dataset, table_name)) -%}

Django reverse db lookup for multiple objects

Let's say I have a model called TicketSection and TicketSubmission the models look like this: (simplified)
class TicketSection(models.Model):
title = models.CharField(max_length="35")
weight = models.IntegerField()
class TicketSubmission(models.Model):
ticket_section = models.ForeignKey('myapp.TicketSection')
cost = models.IntegerField()
submiter = models.ForeignKey('myapp.User')
submiting_account = models.ForeignKey('myapp.Account')
Now I want to filter TicketSections with it's TicketSubmissions. So I do:
ticket_sections = TicketSection.objects.filter(weight__gte=50).annotate(some_additional_logic)
# Generated SQL Query: SELECT * FROM myapp_ticketsection
WHERE weight >= 50
I know you can select reverse objects by
ticket_sections[0].ticketsubmission_set.filter(again_some_logic).annotate(logic)
# Generated SQL Query: SELECT * FROM myapp_ticketsubmission
WHERE ticket_section = [id of ticket section]
But how do I do it for all ticket sections and how do I use it in template? Should I do:
for ticket_sec in ticket_sections:
ticket_sec.submissions = ticket_sec.ticketsubmission_set.filter(again_some_logic).annotate(logic)
and then in template
{% for ticket_sec in ticket_section %}
{% for submission in ticket_sec.submissions %}
{{ submission.cost }}
^ But that doesn't seem like the right way for me. So how should I reverse lookup for multiple objects to minimalize database hits?
I'm using Django 2.2.5 with MySQL database.

How do I merge two queries in django and select distinct from two unrelated tables

I'm working in Django (1.8) with a MySQL backend. I have two unrelated tables that I want to query. The results are a concatenation of two columns from each table. I want to merge the results, sort them and select distinct. For the sake of example let's assume I have tableA and tableB both of which have first_name and last_name columns. Each cloumn heading has a reference to the table in it (e.g. first_name_A). Currently I'm running a query on each table and using sorted(chain(queryA, queryB)) to put them together. The problem comes when I try to reference the result in a for loop in the template.
Codewise I have this:
views.py
queryA = tableA.objects.order_by('first_name_A', 'last_name_A').values('first_name_A', 'last_name_A').distinct()
queryB = tableB.objects.order_by('first_name_B', 'last_name_B').values('first_name_B', 'last_name_B').distinct()
query = sorted(chain(queryA, queryB))
return render_to_response('results.html', {'queries': query})
html
<div>
{% for query in queries %}
<tr>
<td>{{ query.first_name_A }} {{ query.last_name_A }}</td>
</tr>
{% endfor %}
</div>
The above html will obviously only return the first and last names from tableA. These are sorted and distinct but no info from tableB is included. As the columns from each table have different headings and no properties alike other than the values (rather than the keys) how can I merge them, sort and select distinct combinations? Or should I just rename the columns to remove the table reference?
You can use the extra() modifier to assign alternative names.
queryA = tableA.extra(select={'alias' : 'first_name_A'}).order_by('first_name_A').values('alias').distinct()
queryB = tableB.extra(select={'alias' : 'first_name_B'}).order_by('first_name_B').values('alias').distinct()
The django documentation discourages the use of extra but there doesn't seem to be an easy way to create aliases in a queryset. This ticket suggest that this may change in the future. The last comment describes an alternative method with F expressions and annotate which should work with django v1.8
I've solved it but I'm not sure it's the most pythonic (nor correct) way. If anyone has any better suggestions then please let me know.
views.py
queryA = tableA.objects.order_by('first_name_A','last_name_A').values('first_name_A', 'last_name_A').distinct()
queryB = tableB.objects.order_by('first_name_B','last_name_B').values('first_name_B', 'last_name_B').distinct()
chain_query = sorted(chain(queryA, queryB))
valueList = []
for q in chain_query:
wholeName = ' '.join(q.values())
valueList.append(wholeName)
query = sorted(set(valueList))
return render_to_response('results.html', {'queries': query})
html
{% for query in queries %}
<tr>
<td>{{ query }}</td>
</tr>
{% endfor %}

Django: Aggregate a Raw SQL with LEFT JOIN, NO Foreign Keys, AND inject to DetailView (context)

I have been revamping my webapp with Django 1.7. It has been a lot of fun... and tears and blood!
Thing is I've been struggling for the last few days to do a simple LEFT JOIN to aggregate some values from a table with no FKs. The query result should go to a Class View (DetailView). Believe me that I have searched and searched for an answer in the entire web (including the World Wide Web) to no avail.
You might ask why is it that my tables have no Foreign Keys? Well, the original database design hadn't any and now the tables hold hundreds of millions of rows. I could add FK constraints but that would be costly, but it would brake things, and would require to remake entire scripts that do extraction and loading!
I thought falling back to the good old raw SQL because, according to Django,
raw() has a bunch of other options that make it very powerful...
Yeah, right. The truth is model.objects.raw() that its power is limited and it doesn't work for what I want to do (it just doesn't aggregate).
Tables/Models (simplified)
Table `customer` (customer_id, order_id)
Table `order` (order_id, order_name)
MySQL/Django query (simplified)
'SELECT a.order_id, SUM(a.order_value)
FROM order a
LEFT JOIN customer b
ON a.order_id = b.order_id
WHERE b.customer_id = %s', [customer_id]
It looks so innocent, right? Hell no! It's a Django nightmare! Of course, I could easily do that in Django with a __set, but alas, I don't have FKs.
On top of my problem I am trying to add aggregates to a context in my DetailView template. So I tried hacking it with View() and made a function inside my DetailView custom class:
def NewContextFTW():
# here get the freaking queryset in my own terms
return myhighlycomplexqueryset
And then in template:
{% for rows in view.NewContextFTW %}
{{rows.id}}
{{rows.sum_order_value}}
{% endfor %}
...but it failed.
Edit:
I found a solution today! And I want to share the love to the world! See my answer below.
This solution solved my problem. Hope it solves yours too.
Override get_context_data, execute SQL directly through a connection, convert resulting list into a dictionary, add it to context, enjoy:
def get_context_data(self, **kwargs):
# get context from the class this very function belongs to
context = super(MyClassView, self).get_context_data(**kwargs)
user = self.user # example
cursor = connection.cursor()
cursor.execute('SELECT a.order_id, SUM(a.order_value) FROM order a LEFT JOIN customer b ON a.order_id = b.order_id WHERE b.customer_id = %s', [user])
d = dictfetchall(cursor)
new_object_list = []
for i in range(len(d)):
order_id = d[i]['order_id']
sum_order_value = d[i]['SUM(a.order_value)']
row = aggregated_row(order_id, sum_order_value)
new_object_list.append(row)
context['aggregated_values'] = new_object_list
return context
To make that work don't forget to:
from django.views.generic import DetailView
from django.db import connection
...then define the following function to convert the list into a dictionary (this boilerplate comes from Django's 1.7 own documentation):
def dictfetchall(cursor):
"Returns all rows from a cursor as a dict"
desc = cursor.description
return [
dict(zip([col[0] for col in desc], row))
for row in cursor.fetchall()
]
...create a class to hold your rows as objects:
class aggregated_row(object):
def __init__(self, order_id, sum_order_value):
self.order_id = order_id
self.sum_order_value = sum_order_value
...and last but not least, the template:
{% for rows in aggregated_values%}
<tr>
<td>{{rows.order_id}}</td>
<td>{{rows.sum_order_value}}</td>
</tr>
{% endfor %}

Transforming data in Django. Want to be able to apply sums to a decimal field

I have a model like this.
#models.py
class Payment(models.Model):
unit_price = models.DecimalField(max_digits=12, decimal_places=2)
discount = models.DecimalField(max_digits=12, decimal_places=2)
payment_terms = models.CharField(max_length=80)
amount = models.DecimalField(max_digits=12, decimal_places=2)
VAT = models.DecimalField(max_digits=4, decimal_places=2)
def __unicode__(self):
return unicode(self.unit_price)
class Invoice(models.Model):
client = models.ForeignKey(Client)
payment = models.ForeignKey(Payment)
date = models.DateField()
invoice_no = models.CharField(max_length=16)
work_orders = models.ManyToManyField(Work_Order)
contract_info = models.ForeignKey(Contract_Info)
def __unicode__(self):
return self.invoice_no
What I want to focus is payment so try forgetting everything else. Here is my views.py.
#views.py
#login_required
def invoice_details(request, id=1):
invoices_list = Invoice.objects.filter(pk=id)
invoice = get_object_or_404(Invoice, pk=id)
client = invoices_list[0].client
work_orders = invoices_list[0].work_orders
payment = invoices_list[0].payment
return render_to_response(('invoice_details.html', locals()), {'work_orders': work_orders,'payment':payment,'client': client, 'invoice': invoice ,'invoices_list': invoices_list}, context_instance=RequestContext(request))
And here is my template.
<h1>invoice_details.html<h1>
{{ payment.amount }}
The template displays the payment amount in a decimal value. What I want to be able to do is some sums with {{payment.amount}}. Suppose if I wanted to multiply whatever the value
{{payment.amount}} by let say 2. How would I do this?
I would do this in the view,
new_amount = paymount.amount * 2
return render_to_response(('invoice_details.html', locals()), {'work_orders': work_orders,'payment':payment,'client': client, 'invoice': invoice ,'invoices_list': invoices_list, 'new_amount':new_amount}, context_instance=RequestContext(request))
But if there's some reason you cannot or do not want to, you can acheive something similar in the template by using template tags. I do not think there is a multiply tag, but there is an add:
{{ payment.amount|add:"2" }}
Would simply add 2 to your payment amount, similarly
{{ payment.amount|add:payment.amount }}
Would double the value displayed. Adding is likely not exactly what you wanted to do, so you can attempt to make your own template tag to use multiply or whatever function you'd like, see http://docs.djangoproject.com/en/dev/howto/custom-template-tags/
I don't know why you need this to be done inside the template and not in the view, but here is my go at a solution. There is a long discussion about arithmetic in Django templates about not supporting this feature for specifically the use case you described.
If you still want to proceed, this might help. In the Django docs, there is an |add built-in template filter that seems to be a primitive form of arithmetic. Unfortunately, there is no |multiply filter. You can probably find the filter in the template/defaultfilters.py under your Django installation.
Copy it, change what you need to make it a product instead of a sum. When you're done, you might want to submit it to the Django project to make it a buil-tin in future releases.
Here is the answer:
#views.py
subtotal = payment.amount * 2
return render_to_response(('invoice_details.html', locals()), {'work_orders': work_orders,'payment':payment,'client': client, 'invoice': invoice ,'invoices_list': invoices_list, 'subtotal':subtotal}, context_instance=RequestContext(request))
In a template put this custom tag (I think that what it is) to represent the value.
{{ subtotal }}