Django cannot display model data with dynamic fields - python

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.

Related

Render label names instead of integer fields in Django templates

As part of my models, I have an IntergerField "choices".
These choices have been labelled. (Label 1 = Straw, Label 2 = Yellow...)
However, the HTML renders the integer rather than the actual labels.
What do I need to do return the labels and not the field's interger?
Is it somethng I need to do in views? Or do I address it directly in the html file?
Code below:
Models
CHOICE1=(
('',''),
(1,'Straw'),
(2,'Yellow'),
)
class Model1(models.Model):
user = models.ForeignKey(User,blank=True,on_delete=models.CASCADE)
Choice_A = models.IntegerField(choices=Choice1,default=0)
Views
def account(request):
review_list = Model1.objects.all
return render(request,"main/account.html", {'review_list':review_list})
HTML
<h6>Champagnes</h6>
{% for Model1 in review_list%}
<table class="table table-hover table-striped table-bordered ">
{% if Model1.user == user%}
<tr>
<th>Text</th><th>{{Model1.Choice_A }}</th>
</tr>
{%endif%}
</table>
{% endfor %}
You need to use the following in your template:
{{ Model1.get_Choice_A_display }}
It will render the string instead of the integer
You do this with the .get_fieldname_display(…) method [Django-doc], so here get_Choice_A_display:
<th>Text</th><th>{{Model1.get_Choice_A_display }}</th>
Note: normally the name of the fields in a Django model are written in snake_case, not PascalCase, so it should be: choice_a instead of Choice_A.

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.

Drop down lists not populating in DB in Django Forms on click of submit button

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.

How to customize a django form (adding multiple columns)

Currently, I am building a form using the default django template like this:
class old_form(forms.Form):
row_1 = forms.FloatField(label='Row 1')
row_2_col_1 = forms.FloatField(label='Row 2_1')
row_2_col_2 = forms.FloatField(label='Row 2_2')
html = str(old_form())
However, I would like to add multiple columns to my template, and still use django forms object to define parameters.
New temp. should something like (or it can loop through all the variables):
def getdjtemplate():
dj_template ="""
<table>
<tr>{{ table.row_1 }}</tr>
<tr>
<td>{{ table.row_2_col_1 }}</td>
<td>{{ table.row_2_col_2 }}</td>
</tr>
"""
return dj_template
djtemplate = getdjtemplate()
newtmpl = Template(djtemplate)
My question is how to 'combine' the new template and class old_form()?
Thanks for the help!
You can customize the form HTML using its fields, as shown in the documentation. You are doing this in an unusual way; normally you would have the template in a file instead of returning it from a function, but you can still do this:
from django.template import Context
def getdjtemplate():
dj_template = """
<table>
{% for field in form %}
<tr>{{ field }}</tr>
{% endfor %}
</table>
"""
return dj_template
form = old_form()
djtemplate = getdjtemplate()
newtmpl = Template(djtemplate)
c = Context({'form': form})
newtmpl.render(c)

Display table of objects django

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>

Categories