Django Simple History - History Diffing - django

So I'm using django-simple-history for one of my projects. I'm using it on one of the models called "Address" to show the history of the records.
I've created a DetailView to show the information about the address and added context['history'] to show changes of the record. This works all fine.
I would be interested in what field changed, and I read the following; History Diffing
So I somehow need to loop through all the fields from the last two records and find the field which has changed...
I couldn't find any examples on how to achieve this so I tried adding the context to the view
#views.py
class Address(DetailView):
'''
Show details about the address
'''
model = Address
'''
Add history context to the view and show latest changed field
'''
def get_context_data(self, **kwargs):
context = super(Address, self).get_context_data(**kwargs)
qry = Address.history.filter(id=self.kwargs['pk'])
new_record = qry.first()
old_record = qry.first().prev_record
context['history'] = qry
context['history_delta'] = new_record.diff_against(old_record)
return context
And a simple model
#models.py
class Address(models.Model)
name = models.CharField(max_length=200)
street = models.CharField(max_length=200)
street_number = models.CharField(max_length=4)
city = models.CharField(max_length=200)
Template
#address_detail.html
<table>
<thead>
<tr>
<th scope="col">Timestamp</th>
<th scope="col">Note</th>
<th scope="col">Edited by</th>
</tr>
</thead>
<tbody>
{% for history in history %}
<tr>
<td>{{ history.history_date }}</td>
<td>{{ history.history_type }}</td>
<td>{{ history.history_user.first_name }}</td>
</tr>
{% endfor %}
</tbody>
</table>
Somehow this doesn't feel right, there should be a way to iterate over the changes and only add the changed fields to the context...
Any ideas would be appreciated!

I have recently worked on this. I think you are missing a trick you have changes stored in history_delta. You can use this to show the changed fields.
Following will display tabulated result like which field was changed and what was the old and the new value of that field.
{% if history_delta %}
<h3>Following changes occurred:</h3>
<table>
<tr>
<th>Field</th>
<th>New</th>
<th>Old</th>
</tr>
{% for change in delta.changes %}
<tr>
<td>
<b>{{ change.field }}</b>
</td>
<td>
<b>{{ change.new }}</b>
</td>
<td>
<b>{{ change.old }}</b>
</td>
</tr>
{% endfor %}
</table>
{% else %}
<p>No recent changes found.</p>
{% endif %}

Related

Django PDF Render, aggregate template tag shows up with code included

In views.py, I have the following function to render a PDF view:
def agent_render_pdf(request, *args, **kwargs):
pk = kwargs.get('pk')
agent = get_object_or_404(Agents, pk=pk)
invoices = Invoice.objects.filter(agent=agent)
invoice_total = invoices.aggregate(Sum('invoice_amount'))
template_path = 'agents/statement.html'
context = {'agent': agent,
'invoices':invoices,
'invoice_total': invoice_total,}
#Create a Django response object, and specify content_type as pdf
response = HttpResponse(content_type='application/pdf')
#if download:
#response['Content-Disposition'] = 'attachment'; filename"report.pdf"'
#if display:
response['Content-Disposition']='filename="report.pdf"'
#find the template and render it
template = get_template(template_path)
html = template.render(context)
#create a PDF
pisa_status = pisa.CreatePDF(
html, dest=response
)
if pisa_status.err:
return HttpResponse('We had some errors <pre>' +html +'</pre>')
return response
I am rendering that to 'statements.html' as follows:
<body>
{{ agent }}
<div class="table">
<table>
<th>Invoice Number</th>
<th>Invoice Date</th>
<th>Due Date</th>
<th>Invoice Amount</th>
<th>Invoice Age</th>
{% for i in invoices %}
<tr>
<td>{{ i.invoice_number }}</td>
<td>{{ i.invoice_date }}</td>
<td>{{ i.invoice_due_date|date:"m/d/Y" }}</td>
<td>{{ i.invoice_amount }}</td>
<td>{{ i.InvoiceAge }}</td>
</tr>
{% endfor %}
<tr>
<td> </td>
<td> </td>
<td>total:</td>
<td>${{ invoice_total }}</td>
<td> </td>
</tr>
</table>
</div>
My view is basically rendering as expected, accept that the invoice_total is showing up as
${'invoice_amount__sum':
Decimal('2500')}
instead of just proving the total of invoices as $2,500. I'm clearly getting the information I want, I just don't understand what I'm doing wrong that's causing the formatting issue?
The full PDF is:
Screen capture of PDF output
.aggregate() returns a dictionary, because you can get several aggregates at the same time (imagine selecting a count, a max, and a min at once, to avoid multiple queries)
So just set a key and use it:
invoice_total = invoices.aggregate(total=Sum('invoice_amount'))['total']

Django - Dynamic filters in a webpage

I am still a learner for python django. I’d like to filter dynamically data on the table below
table
The informations in my html template are :
<table>
<thead>
<tr>
<th>N°Commande</th>
<th>Magasin</th>
<th>Objet</th>
<th>Date commande</th>
<th>Montant</th>
<th>Etat</th>
</tr>
</thead>
<tbody>
{% for item in query_results %}
<tr>
<td>{{ item.numero_commande }}</td>
<td>{{ item.enseigne}}</td>
<td>{{ item.Objet}}</td>
<td>{{ item.Date_commande}}</td>
<td>{{ item.Montant}}</td>
<td>{{ item.Etat}}</td>
</tr>
</tbody>
from the class : class Commande(models.Model)
Here is an exemple of what I’d like to have (filters on table fields) :
table with dynamic filters
thank you in advance for your help
Vinloup
There's a couple of routes you could take to accomplish this, but as a beginner I think you should take a look at the django-filter library. Setup is quite simple:
pip install django-filter
Then add 'django_filters' to your INSTALLED_APPS.
INSTALLED_APPS = [
...
'django_filters',
]
Then you create a FilterSet
import django_filters
class ProductFilter(django_filters.FilterSet):
name = django_filters.CharFilter(lookup_expr='iexact')
class Meta:
model = Product
fields = ['price', 'release_date']
In your view you want somethign like this (for function based views)
def product_list(request):
f = ProductFilter(request.GET, queryset=Product.objects.all())
return render(request, 'my_app/template.html', {'filter': f})
And finally in your template you want something like this
...
<form method="get">
{{ filter.form.as_p }}
<input type="submit" />
</form>
<tbody>
{% for obj in filter.qs %}
<tr>
... render your table rows here using {{ obj }}
</tr>
{% endfor %}
</tbody>
...
You could also try different setups and the documentation on the library gives you a few examples.
You can filter dynamically the context in the view function that renders this page. So when you complete the form's fields, it makes a GET request to the server, the parameters that you need to filter the data are in this request. Then, you only need to filter the query with this parameters. Here you have the Django guide for make queries. Making queries | Django
I suggest you show the function code on your view.py file so we can understand how you are filtering the data.
You can filter the table with JavaScript and a input. If you are using Bootstrap:
https://www.w3schools.com/bootstrap/bootstrap_filters.asp
Template code with Bootstrap would look like this:
<input class="form-control" id="myInput" type="text" placeholder="Search..">
<table>
<thead>
<tr>
<th>N°Commande</th>
<th>Magasin</th>
<th>Objet</th>
<th>Date commande</th>
<th>Montant</th>
<th>Etat</th>
</tr>
</thead>
<tbody id="myTable">
{% for item in query_results %}
<tr>
<td>{{ item.numero_commande }}</td>
<td>{{ item.enseigne}}</td>
<td>{{ item.Objet}}</td>
<td>{{ item.Date_commande}}</td>
<td>{{ item.Montant}}</td>
<td>{{ item.Etat}}</td>
</tr>
{% endfor %}
</tbody>
</table>
<script>
$(document).ready(function(){
$("#myInput").on("keyup", function() {
var value = $(this).val().toLowerCase();
$("#myTable tr").filter(function() {
$(this).toggle($(this).text().toLowerCase().indexOf(value) > -1)
});
});
});
</script>

how to get flat value from muitiple values_list? Django database

I am getting postgresql data from my server and fill out the table down below.
I was able to join 3 different tables and get columns I want from them.
However, it gives me none-formatted object. Is there any way I can fill out the table using joined query one by one..?
enter image description here
enter image description here
views.py
vlan_query_results = Vlans.objects.select_related('vlan_id').values_list('name', 'gateways__vip', 'gateways__backup', 'gateways__master', 'gateways__nat', 'gateways__vhid', 'subnets__dhcp', 'subnets__dns').order_by('vlan_id')
template
{% for object in vlan_query_results %}
<tr #id='items-table'>
<th scope="row" class="checkbox-row"><input type="checkbox" name="item" /></th>
<th scope="row">{{ forloop.counter }}</th>
<td class="item"> {{object }}</td>
<td class="item"> </td>
</tr>
{% endfor %}
Maybe instead using values_list, you should use DRF Serializers to get the response in a dictionary like structure with only the fields you want.
from rest_framework import serializers
class VlansSerializer(serializers.ModelSerializer):
gateways_vip = serializers.ReadOnlyField(source='gateways__vip')
gateways_backup = serializers.ReadOnlyField(source='gateways__backup')
gateways_master = serializers.ReadOnlyField(source='gateways__master')
gateways_nat = serializers.ReadOnlyField(source='gateways__nat')
gateways_vhid = serializers.ReadOnlyField(source='gateways__vhid')
subnets_dhcp = serializers.ReadOnlyField(source='subnets__dhcp')
subnets_dns = serializers.ReadOnlyField(source='subnets__dns')
class Meta:
model = Vlans
fields = ('name', 'gateways_vip', 'gateways_backup', 'gateways_master', 'gateways_nat', 'gateways_vhid', 'subnets_dhcp', 'subnets_dns')
In your views.py:
vlan_query_results = VlansSerializer(Vlans.objects.all(), many=True).data
In your html:
{% for object in vlan_query_results %}
<tr>
<th scope="row" class="checkbox-row">
<input type="checkbox" name="item" />
</th>
<th scope="row">{{ forloop.counter }}</th>
<td class="item">{{object.name}}</td>
<td class="item">{{object.gateways_vip}}</td>
</tr>
{% endfor %}
You have to add the rest of the <td></td> tags in a similar manner.
Also, you cant add the #id='items-table' to the <tr> since it is in a loop and ids are supposed to be unique in a html page.
Hope this helps. :)

Not able to display the Content of Table

models.py has
class Question(models.Model):
Question_Id = models.AutoField(primary_key=True)
Question_Text = models.TextField(max_length=1000)
def __str__(self):
return self.Question_Text
def __int__(self):
return self.Question_Id
class QuestionChoices(models.Model):
Choice_Id = models.AutoField(primary_key=True)
Question_Choices_Question_Id = models.ForeignKey("Question", on_delete=models.CASCADE)
Choice = models.TextField(max_length=500)
Is_Right_Choice = models.BooleanField(default=False)
views.py file has the below
def QuestionDisplay(request):
retrieved_questions_id = Question.objects.values("Question_Id")
retrieved_questions = Question.objects.filter(Question_Id = retrieved_questions_id)
display_content = {retrieved_questions_id: retrieved_questions}
return render(request, 'cgkapp/Question_page.html', context=display_content)
templates folder Question_page.html has
{% block body_block %}
<table>
<thead>
<th>Questions</th>
</thead>
{% for quest in display_content %}
<tr>
<td>{{quest.Question_Text}</td>
</tr>
{% endfor %}
</table>
{% endblock %}
The page displays the Questions as the Table header but not the actual questions from the models. What could be the reason?
You have a typo, you have missed } after Question_Text}
<td>{{quest.Question_Text}}</td>
Update After Comment
Do like below, don't know why but you are getting very much confused I think. It will solve your problem
models.py
class Question(models.Model):
# `id` primary key is already present no need to define.
question_text = models.TextField(max_length=1000)
def __str__(self):
return self.question_text
views.py
def question_display(request):
# There is no need of 2 queries, you can get that in single query.
questions = Question.objects.all();
# If you want only question ids
question_ids = Question.objects.values('id')
# You can send multiple variables to the template using context dictionary and directly access them there.
context = {'questions': questions, 'question_ids':question_ids}
return render(request, 'cgkapp/Question_page.html', context)
In template
#Show questions with only question texts
<table>
<thead>
<th>Questions</th>
</thead>
{% for quest in questions %}
<tr>
<td>{{quest.question_text}}</td>
</tr>
{% endfor %}
</table>
# If you also want to show question text with ids.
<table>
<thead>
<th>Question ID</th>
<th>Question Text</th>
</thead>
{% for quest in questions %}
<tr>
<td>{{quest.id}}</td>
<td>{{quest.question_text}}</td>
</tr>
{% endfor %}
</table>
# If you want to show only Question IDs
<table>
<thead>
<th>Question ID</th>
</thead>
{% for quest in question_ids %}
<tr>
<td>{{quest}}</td>
</tr>
{% endfor %}
</table>
#This same thing can be done this way also.
<table>
<thead>
<th>Question ID</th>
</thead>
{% for quest in questions %}
<tr>
<td>{{quest.id}}</td>
</tr>
{% endfor %}
</table>
If it was just the typo, you'll see the string {{quest.Question_Text} being rendered. The problem is with your context. You're sending a dictionary there where keys are supposed to be strings,
display_content = {'retrieved_questions_id': retrieved_questions}
And then, in your template, you would iterate over retrieved_questions_id and not display_content. display_content is just the name of the dictionary where you're storing the values.

Django: List all where one field combines all its unique values

I got stuck. Problem I have is that I would like to create a list of objects.all() but where all objects, where one ForeignKey is the same, should be combined to one entry in a list.
My Model:
class TournamentStandings(models.Model):
tournament = models.ForeignKey(Tournament, on_delete=models.CASCADE)
player = models.ForeignKey(Player, on_delete=models.CASCADE)
player_place = models.FloatField(verbose_name=u"Place")
The list I would like to get is something like this:
ID | Player | Tournament| Place
1 | Name_1 | Tournament_1, Tournament_2| Place_on_tournament_1, P_o_t_2
2 |Name_2 |Tournament_1, Tournament_2| Place_on_tournament_1, P_o_t_2
So the ForeignKey(Player) would be the one I would like to limit and combine entries. I tried the generic view for objects.all() and the loop in my template:
{% for player in ranking_list %}
<tr>
<td>{{ ranking_list.id }}</td>
<td>{{ ranking_list.player }}</td>
<td>{{ ranking_list.tournament }}</td>
<td> {{ ranking_list.player_place }} </td>
</tr>
{% endfor %}
It didn't work. Any hints ??
Add an order_by to your query so that standings for the same player are sequential, and pass the result into itertools.groupby to get "sublists" (all standings for the same player grouped together). You can then process/format those in any way you like.
I used a little bit different aproach.
I used annotate in my views.py:
ranking_list = TournamentStandings.objects.values('player__name').annotate(Sum('player_points'))
return render(request, 'rankings.html', {'ranking_list': ranking_list})
Then in my template I show column with player__name and player_points_sum. Sorting is done by webstack_django_sorting in template so users can decide by wich column and what order they want the results are shown.
template.html:
{% load sorting_tags %}
<table class="table table-bordered">
<thead>
<tr>
<th>{% anchor player__name _("Gracz") %}</th>
<th>{% anchor player_points__sum _("Punkty") %}</th>
</tr>
</thead>
{% load static %}
<tbody>
{% autosort ranking_list %}
{% for ranking_list in ranking_list %}
<tr>
<td>{{ ranking_list.player__name }}</td>
<td> {{ ranking_list.player_points__sum }} </td>
</tr>
{% endfor %}
</tbody>
</table>