I need to display a table from my database with Django. The obvious way is to manually type in the table headings and loop through query results of model.objects.all(). However, being quite lazy, I want to do this automatically, i.e. load all fields from model through introspection to display as column headings and load all field values to display as rows. This approach can also save me some time later because I don't have to update my template code when my model changes. I got it to work but there are two problems:
I can't find away to load the AutoField field (id) value so I have to slice off the ID column.
The code looks quite messy especially with the use of random template tags.
Here is my code. Please note that the code works fine so I'll skip all the imports as they are correct:
views.py I use serializers to serialize the data, a trick I read somewhere on stackoverflow
def index(request):
fields = MyModel._meta.fields
data = serializers.serialize("python", MyModel.objects.all())
context_instance = RequestContext(request, {
'data' : data,
'fields' : fields,
})
return TemplateResponse(request, 'index.html', context_instance)
template/index.html: note that I have to slice off the ID column by slicing off the first element of the fields list
{% with fields|slice:"1:" as cached_fields %}
<table>
<thead>
<tr>
{% for field in cached_fields %}
<th>{% get_verbose_name field %}</th>
{% endfor %}
</tr>
</thead>
<tbody>
{% for instance in data %}
<tr>
{% for field in cached_fields %}
<td>{% get_value_from_key instance.fields field %}</td>
{% endfor %}
</tr>
{% endfor %}
</tbody>
</table>
{% endwith %}
templatetags/extra_tags.py
# tag to get field's verbose name in template
#register.simple_tag
def get_verbose_name(object):
return object.verbose_name
# tag to get the value of a field by name in template
#register.simple_tag
def get_value_from_key(object, key):
# is it necessary to check isinstance(object, dict) here?
return object[key.name]
serializers.serialize("json or xml", Model.objects.all()) formats return the id field; probably not what you are looking for but some of the jQuery grid plugins can further automate the task.
Yay! I found a work around thanks to Видул Петров's suggestion about serializing the data to json, which allows me to load the pk field as well. It still feels too manual and hackish (and verbose) but I think I'm getting close. Please help me refactor this code further.
views.py Serialize data into a list of JSON objects and parse it into a list of dictionaries to pass it to template
from django.utils import simplejson as json
def index(request):
fields = MyModel._meta.fields
data = json.loads(serializers.serialize("json", MyModel.objects.all()))
def parse_data(data):
result = []
# flatten the dictionary
def flatten_dict(d):
"""
Because the only nested dict here is the fields, let's just
remove the 'fields' suffix so that the fields can be loaded in
template by name
"""
def items():
for key, value in d.items():
if isinstance(value, dict):
for subkey, subvalue in flatten_dict(value).items():
yield subkey, subvalue
else:
yield key, value
return dict(items())
for d in data:
# change the 'pk' key name into its actual name in the database
d[Employee._meta.pk.name] = d.pop('pk')
# append the flattend dict of each object's field-value to the result
result.append(flatten_dict(d))
return result
context_instance = RequestContext(request, {
'data' : parse_data(data),
'fields' : fields,
})
return TemplateResponse(request, 'index.html', context_instance)
template/index.html The template is much nicer now
<table>
<thead>
<tr>
{% for field in cached_fields %}
<th>{% get_verbose_name field %}</th>
{% endfor %}
</tr>
</thead>
<tbody>
{% for d in data %}
<tr>
{% for field in fields %}
<td>{% get_value_from_key d field %}</td>
{% endfor %}
</tr>
{% endfor %}
</tbody>
</table>
Related
I have a table inside form. It looks like below:
{% extends "base.html" %}
{% block title %}Title{% endblock title %}
{% block content %}
<form actions="" method="post">
{% csrf_token %}
<table>
<table border = "1" cellpadding = "10" cellspacing = "10" bordercolor = "green">
<tr>
<th>numbers</th>
<th>Extension</th>
<th>Vendor</th>
</tr>
{% for number in numbers %}
<tr>
<td>{{ number }}</td>
<td class = "select">Select Extension
<select name="extensions">
{% for obj in sipextensionsets %}
<option value={{obj.sip_extension}}>{{ obj.sip_extension }}</option>
{% endfor %}
</select>
</td>
<td>vendor</td>
</tr>
{% endfor %}
</table>
<input type="submit" value="save"/>
</form>
{% endblock content %}
My forms.py is below:
from django import forms
from .models import column
class didsForm(forms.ModelForm):
class Meta:
model = column
fields = ('extension')
My views.py is below
def saveintodb(request):
try:
instance = coloumn.objects.get(pk=1)
except:
instance = coloumn(pk=1)
instance.save()
if request.method == 'POST':
dids_form = didsForm(data=request.POST['extensions'], instance=instance)
if dids_form.is_valid():
dids_form.save()
messages.success(request, "Settings updated. Please apply settings.")
else:
messages.error(request, "Error: Invalid settings.")
else:
dids_form = didsForm(instance=instance)
return render(request, 'dids/index.html', {'dids_form': dids_form})
In the table, there is a drop down (select tag). I want to save the data into database when user selects something from dropdown and clicks on save button. I know I have mistaken somewhere in views.
You're doing a few things wrong here, unfortunately.
The main problem is that you're passing request.POST['extensions'] as the data argument to your form on POST; but that argument is expecting the whole POST, not a single field.
Linked to that is that you have not used the same name for the field in the model and the field in the form. Although you say in your comment that this is intentional, there doesn't seem to be a reason for it, and it's breaking things. Give them the same name.
Thirdly, you aren't letting Django populate the form, or show any errors when it's not valid. You shouldn't be explicitly passing sipextenionset (although you actually don't seem to be passing that at all, so I'm not sure where it's coming from), and you certainly shouldn't be explicitly iterating. You should let Django display the field:
<td>{{ number }}</td>
<td class="select"><label for="id_extension">Select Extension</label>
{{ form.extension }}
</td>
Finally, I can't at all understand what you are doing with that outer for loop through numbers; you will end up with several values for extension, which is not expected by your form, your model, or your view.
I got a view that receives a model and field the user passes via post (existing model and field inside the app of course) and makes a queryset filter on it, then i need to show in my template that result in a table (name fields must be the column headers) and their respective values.
This what i got so far trying to serialize the queryset result in order to make it easier to show in template:
Views.py:
from django.contrib.contenttypes.models import ContentType
class CommitteeReport(BaseView):
template_name = 'committee/committee_report.html'
def post(self, request, **kwargs):
myfield = request.POST['field'].lower()
my_model = request.POST['model'].lower()
queryset_obj = ContentType.objects.get(model = my_model).model_class().objects.filter(**{myfield:True})
return render(request, self.template_name,{
'requirements': queryset_obj,
})
And my template:
<div class="tab-pane active" id="tab_1">
<table class="datatable table table-striped table-hover" cellspacing="0">
<thead>
<tr>
{% for key in requirements %}
<th>{{ key.fields.name }}</th>
{% endfor %}
</tr>
</thead>
<tbody>
{% for item in requirements %}
<tr>{{ item.fields.value }}</tr>
{% endfor %}
</tbody>
</table>
</div>
Thing is, i don't get result or if i change the tag inside the template, i get the objects dictionary for every row.
Any idea of how to achieve what i need ?, thanks in advance.
You can get the list of fields of model using my_instance._meta.local_fields. So you could try this in your view before return:
if queryset_obj.exists():
fields = queryset_obj.first()._meta.local_fields
else:
fields = []
return render(request, self.template_name,{
'requirements': queryset_obj,
'fields': fields,
})
And then using the context variable fields in your template:
<table class="datatable table table-striped table-hover" cellspacing="0">
<thead>
<tr>
{% for field in fields %}
<th>{{ field.name }}</th>
{% endfor %}
</tr>
</thead>
<tbody>
{% for req in requirements %}
<tr>
{% for field in fields %}
<td>{{ req|lookup:field.name }}</td>
{% endfor %}
</tr>
{% endfor %}
</tbody>
</table>
where you define a custom lookup template filter to fetch the field value:
def lookup(model, attr):
if hasattr(model, attr):
return getattr(model, attr)
else:
return None
Note: I'm assuming that all requirements are the same class and have the same structure, so that requirements.0, the first object in the QuerySet, is used for making the headers of the table.
Note 2: I haven't tried this, you might move some of the work to the view and pass easier variables as context variables (e.g. the list of field names).
Note 3: I haven't added error handling, you should check if model is actually a Model instance for example.
There is a char field named json_field in Django Model. I am trying to iterate it from the view but it returns only one result as the return statement does. I am trying to figure it out how I can iterate json_field using yield.
the result that Model Object returns like:
id : 1
title : "Some Title"
json_field : [{"key":"value","key2":"value2"},{"key":"value","key2":"value2"}]
created : "Sat Oct 21 2017 14:00:53 GMT+0300 (+03)"
view.py
import json
def MyView(request):
model_query = MyModel.objects.all() or MyModel.objects.filter or exclude...
for item in model_query:
data_item = json.loads(item.json_field)
template = "template.html"
context = {"title":title, "data_item":data_item}
return render(request, template, context)
in template.html
{% for query_item in model_query %}
<table>
<thead>
<tr>
<th>{{ query_item.title }} - {{ query_item.created }}</th>
</tr>
</thead>
<tbody>
<tr>
<th>Some Heading </th>
<th>Some Heading </th>
</tr>
<!-- json data -->
{% for item in data_item %}
<tr>
<th>{{ item.key }}</th>
<td>{{ item.key2|floatformat:2 }}</td>
</tr>
{% endfor %}
<!-- json data -->
</thead>
</table><
{% endfor %}
Any help will be appreciated.
You can prepare dataset for you template.
# Fetch data from db as queryset of dicts
items = list(MyModel.objects.filter().values('title', 'created', 'json_field'))
# Decode json in-place
for item in items:
item['json_field'] = json.loads(item['json_field'])
context = {"title":title, "items": items}
Then interate through items inside your template:
{% for item in items %}
<table>
<thead>
<tr>
<th>{{ item.title }} - {{ item.created }}</th>
</tr>
</thead>
<tbody>
<tr>
<th>Some Heading </th>
<th>Some Heading </th>
</tr>
<!-- json data -->
{% for entry in item.json_field %}
<tr>
<th>{{ entry.key }}</th>
<td>{{ entry.key2|floatformat:2 }}</td>
</tr>
{% endfor %}
<!-- json data -->
</thead>
</table><
{% endfor %}
If you're using PostgreSQL, you can using JSONField. It uses the postgres's jsonb type, which is optimized for keeping a json serializable text.
If not, you still can use django-jsonfield. It almost gives the same functionality, even though some of the cool features of django's JSONField are not available (like this kind of lookups).
If none of these work for you, you can also implement your own JSONField by inheriting from CharField or TextField, and overriding some of the functions. This way, you won't need any of the logics of your field in your views.
Edit:
If you find changing your field hard or don't wanna do it for whatever reason, you can do this in your view:
for item in model_query:
item.loaded_json = json.loads(item.json_field)
then you can use it like a normal field in your template:
{% for query_item in model_query %}
{% for item in query_item.loaded_json %}
<span>{{ item.key }}</spam>
{% endfor %}
{% endfor %}
Hello!
The solution depends on your purposes.
Use comprehensions if you want to construct a list of json arrays:
data_items = [json.loads(item.json_field) for item in model_query]
... or generator of json array:
data_items = (json.loads(item.json_field) for item in model_query)
If you want to have a single array of json objects try this:
data_items = []
for item in model_query:
data_items.extend(json.loads(item.json_field))
Then you can use data_items as a template context.
A little tip: You can utilize JSONField at ORM level if you use PostgreSQL or MySQL. Consider this approach if you plan to make any filter queries on this field. As additional benefit JSON encoding/decoding will be out of box.
Thanks for updating your code!
Now I would restructure the json.load() list of dicts so you can use it. That is better style than mangling in the template.
concatenation is done by:
my_dict = dict()
for d in data_item
my_dict.update( d )
if you want to merge, check this thread:
How to merge two dictionaries in a single expression?
My Directories app has a predefined variety of different models each of which needs to be selected and returned with its corresponding objects. As a result, since my models are plenty and the user needs to select each one separately I made a dynamic form that passes the model table name m_tb_name which is retrieved by my views.py and returns the appropriate model as shown here:
*views.py*
def dlist(request):
#get the model table name from the form
m_tb_name= request.POST['model_classes_field']
#retrieve the model using the table name
model_class = get_model('Directories', m_tb_name)
# return all model data
model_list = model_class.objects.all()
# how the data will be handled in list.html template
#get fields and their names
fields = get_model_fields(model_class)
field_names = model_class._meta.get_all_field_names()
print 'You searched for: %r' % m_tb_name
return render(request, 'Directories/list.html', {'m_tb_name':m_tb_name, 'model_class':model_class, 'model_list':model_list, 'fields':fields, 'field_names':field_names})
And since I got the model, its fields and its objects.all() initialized I try to populate a table with the model's data. However, in my template I have this:
*list.html*
<table>
<tr>
{% for f in fields %}
<th>{{ f.verbose_name }}</th>
{% endfor %}
</tr>
{% for f in fields %}
{% for mod in model_list %}
<tr>
<td> {{mod.f }}</td>
</tr>
{% endfor %}
{% endfor %}
</table>
And although I was expecting some data in my mod.f (specifically I was expecting the data for each particular field of the model) the output is empty, nothing is returned. Any ideas of why this is happening would be helpful.
The idea was to do something like the anwser in this question but with dynamic fields not hard coded ones.
Models don't have an f attribute, which is how your template variable is being parsed. If you want to do a dynamic lookup on mod with the value of f, you will need to write a simple template filter.
This is assuming that columns is a list containing Strings, each String is representing one of the object o's variable.
<tbody>
{% for o in objects %}
<tr>
{% for col in columns %}
<td>{{ o.col }}</td>
{% endfor %}
</tr>
{% endfor %}
</tbody>
Example:
class Dog(models.Model):
name = models.CharField()
age = models.IntegerField()
is_dead = models.BooleanField()
columns = ('name', 'age')
I cannot explicitly enter the object's variable name and must pass it as another list because I am trying to make a 'generic' template. Also, not all variables must be shown to the users.
I'm not familiar enough with django to know if there's something builtin for this, but... you could just define your own version of getattr as a template filter. For some reason (I'm assuming because it's a builtin function), I wasn't able to simply register the builtin as a new template filter. Either way, this is how I've defined my version:
# This is defined in myapp/templatetags/dog_extras.py
from django import template
register = template.Library()
#register.filter
def my_getattr(obj, var):
return getattr(obj, var)
To use it, you'll use it just like any other two arg template-filter:
{{ o|my_getattr:col }}
Here's a full example (don't forget the "load" directive at the top!):
{% load dog_extras %}
<table>
<tbody>
{% for o in objects %}
<tr>
{% for col in columns %}
<td>{{ o|my_getattr:col }}</td>
{% endfor %}
</tr>
{% endfor %}
</tbody>
</table>
If you've never made custom template-filters before, be sure to read the docs!