Django - accessing modelform field value in template - python

I have a form with multiple fields, one of which I would like to be able to replace the field's HTML control with just the plain text of the field's value. What do I do in the template, in models.py, or in my forms.py to do this? I have nothing in my forms.py related to that field yet, just other fields.
#models.py
class RoomEvent(models.Model):
room = models.ForeignKey('Room')
description = models.CharField(max_length=250)
-
#template
<form method='POST' action=''>
<table>
<tr>
<td><label for="id_room">Room:</label></td>
<td>{{ form.room.room.name }}</td>
</tr>
...
I have tried this:
#fields.py:
class RoomEventForm(ModelForm):
room = forms.ModelChoiceField(queryset=Room.objects.select_related())
...then replacing {{form.room}} with {{form.room.room.name}} in the template, to no avail. Just gives me a blank.
{{ form.room.name }} gives me "room"..?
{{ form.room.value }} gives me the ID, not the actual name.
Why is this so difficult???

I wound up using {{ roomevent.room.name }} where roomevent is the ModelForm's model. I guess that's the "generally accepted" way of doing this.

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.

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.

Get model attributes without making them required for modelform_factory?

I've got a modelform_factory which works perfect for what I need.
I call it like this:
dep_formset = modelformset_factory(
Dependent,
fields=('has_medical', 'med_group_id'),
max_num = dep_count
)
As you might guess, Dependent has additional fields that I want to use simply for display purposes (first_name and last_name respectively) I want to show the first_name and last_name of the dependent person - but have it be simply for display, not edit.
If I add 'first_name' and 'last_name' to the fields in the above, I can then access them in my template - but the form fails against .is_valid() because I'm displaying them simply as text and not as an input, so they're not being POST'ed
Is there some clever way to get access to the first_name and last_name of the dependent objects without adding them to fields for the formset_factory?
The template looks like this (partial snippet so you can see what I'm trying to do with the first_name, last_name):
{% for form in dep_formset %}
{{ form.id }}
{% for hidden in form.hidden_fields %}
{{ hidden }}
{% endfor %}
<div class="g">
<h4>{{ form.first_name.value }} {{form.last_name.value}}</h4>
</div>
<div class="g dependent-option">
<div class="g-1-4 dependent-option-check">
{% if form.has_medical %}
Based on provided information it looks like the formset is tied to an instance which you can access in template:
{{ form.instance.first_name }}

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 input element error css class

I'd like to know how can I add .error class to input elements (to registration app) when the form validation fails.
If you want to place your error CSS class to form input widgets (not their containers), you can derive your form class from the following one:
class StyledErrorForm(forms.Form):
def is_valid(self):
result = super().is_valid()
# loop on *all* fields if key '__all__' found else only on errors:
for x in (self.fields if '__all__' in self.errors else self.errors):
attrs = self.fields[x].widget.attrs
attrs.update({'class': attrs.get('class', '') + ' is-invalid'})
return result
It's now easy -- new feature in Django 1.2
Just add an attribute on the form class & you're good to go. This feature is mentioned in the docs under a "new in 1.2" note, but you can find the magic at django.forms.forms.BoundField.css_classes Here's the API reference, and an example:
class MyForm(forms.Form):
required_css_class = "required"
error_css_class = "error"
This can be done completely through your template.
You build the form template for each form field that you want to test you can use the following example construct
<input type="text" class="reg-txt{% if form.fieldname.errors %} errors{% endif %}"/>
This lets you provide the interface you want without modifying the view & django form code.
Using a Custom Template...
Personally never had much luck using the built in Django error classing solutions, and besides, I like to use the built in 'striptags' template filter on the errors, to get rid of all the html list stuff which I cant figure out how to render nicely anyway.
I use the following custom template to class them as 'error_id'.
#register.filter(is_safe=True)
#stringfilter
def error_id(value):
if value=='':
return ''
else:
return r'<span class="error_id">'+value+'</span>'
Render the individual errors in your template using:
{{ form.my_field.errors|striptags|error_id}}
Or render the whole form using something like:
<table border="1" cellpadding="5px" align="center">
{% for field in form.visible_fields %}
<tr>
<td> {{ field.label_tag }}: </td>
<td> {{ field }} </td>
<td> {{ field.errors|striptags|error_id }} </td>
</tr>
{% endfor %}
</table>
(Better late than never)
You should be able to do this with Django Uni Form

Categories