Reorder DELETE Field in Formset - python

I have a Django formset that is displayed as a table with one form per table. I would like to add a checkbox in the first column of the table so that the user to check it if they would like to delete the row (form).
I have the javascript to manage the deletion of the formset row (form) and modify the management form on the front end, but I am having an issue when I add the DELETE field to the form. I used the solution reference in modify DELETE widget so that I could add the class "delete" to all of my delete fields for use in CSS and JS on the front end. When the DELETE field is added it is always the last field in the form. I would like it to be first.
models.py
class ModelOne(models.Model):
attr_one = models.CharField(max_length=16)
attr_two = models.CharField(max_length=16)
forms.py
class BaseFormOneFormset(BaseModelFormSet):
def add_fields(self, form, index) -> None:
super().add_fields(form, index)
form.fields[DELETION_FIELD_NAME].widget = forms.CheckboxInput(attrs={'class':"delete"})
form.fields["id"].widget=forms.HiddenInput(attrs={'class':'pk'})
class FormOne(forms.ModelForm):
class Meta:
model = ModelOne
attr_one = forms.CharField(max_length=16,
required=True,
label="attr_one",
widget=forms.TextInput(attrs={'size':5,'class':'required '}))
attr_two = forms.CharField(max_length=16,
required=True,
label="attr_two",
widget=forms.TextInput(attrs={'size':5,'class':'required '}))
views.py
def view_one(request):
formset_factory_one = modelformset_factory( ModelOne,
FormOne,
formset=BaseFormOneFormset,
extra=0,
can_delete=True)
formset_one = formset_factory_one(query_set=FormOne.objects.all(),
prefix="formone")
return render(request, "app_one/template_one.html",{"formset_one":formset_one})
template_one.html
<table id="tbl-id">
<thead id="tbl-head-id">
<tr>
{% for form in formset_one|slice:":1" %}
{% for field in form.visible_fields %}
<th>{{field.label|safe}}</th>
{% endfor %}
{% endfor %}
</tr>
</thead>
<tbody id="tbl-body-id">
{% for form in formset_one %}
<tr id="row{{forloop.counter0}}-id" class="formset-row">
{% for field in form.visible_fields %}
<td>
{{field}}
</td>
{% endfor %}
{% for field in form.hidden_fields %}
<td hidden >{{field}}</td>
{% endfor %}
</tr>
{% endfor %}
</tbody>
</table>
Resulting Table
image of resulting html table
Solutions tried
I have already tried to set the Form.field_order both in the FormOne class declaration
class FormOne(forms.ModelForm):
field_order = [ DELETION_FIELD_NAME, "attr_one", "attr_two"]
and in the BaseFormset.add_fields method
def add_fields(self, form, index) -> None:
super().add_fields(form, index)
form.fields[DELETION_FIELD_NAME].widget = forms.CheckboxInput(attrs={'class':"delete"})
form.fields["id"].widget=forms.HiddenInput(attrs={'class':'pk'})
form.field_order = [ DELETION_FIELD_NAME, "attr_one", "attr_two"]
These both result in the DELETE field still last in the order.

Although there are ways to change the order of the DELETE field by overriding the formset's add_fields() function, it is pretty tedious and kinda complicated.
However, if your goal is to simply render DELETE first, or in a completely different part of the page, you are in luck. This is easily accomplished in the template.
The delete field can be rendered explicitly in the template using {{ form.DELETE }} which is equivalent to the regular output of {{ field }} that you get when iterating through form.fields.
Simple example to render DELETE field first:
{# this renders the DELETE field #}
{{ form.DELETE }}
{# now render the other fields #}
{% for field in form.visible_fields %}
{# if check to prevent DELETE rendering twice #}
{% if field.name != 'DELETE' %}
{{ field }}
{% endif %}
{% endfor %}
Applied to the code from your question:
<table id="tbl-id">
<thead id="tbl-head-id">
<tr>
{% for form in formset_one|slice:":1" %}
{% for field in form.visible_fields %}
<th>Delete</th>
{% if field.name !='DELETE' %}
<th>{{field.label|safe}}</th>
{% endif %}
{% endfor %}
{% endfor %}
</tr>
</thead>
<tbody id="tbl-body-id">
{% for form in formset_one %}
<tr id="row{{forloop.counter0}}-id" class="formset-row">
<td>
{{ form.DELETE }}
</td>
{% for field in form.visible_fields %}
{% if field.name != 'DELETE' %}
<td>
{{field}}
</td>
{% endif %}
{% endfor %}
{% for field in form.hidden_fields %}
<td hidden >{{field}}</td>
{% endfor %}
</tr>
{% endfor %}
</tbody>
</table>

Related

My UpdateView does show errors on fly orm

I have an update view which displays a form (based on some criteria). I can update my model successfully, but if I enter bad data (like a bad time field), I do not get any errors back, I simply get a refresh of the update form.
My view looks like this:
class Cd_MixedView(UpdateView):
model = Track
template_name = 'cd_mixed_view.html'
form_class = TrackForm
context_object_name = 'cd_edit'
def get_context_data(self, **kwargs):
context = super(Cd_MixedView,self).get_context_data(**kwargs)
cur_track = Track.objects.get(id=self.kwargs['pk'])
context['form'] = TrackForm(instance=cur_track)
context['cd_info'] = Cd.objects.get(id=cur_track.cd_id.pk)
return context
def post(self, request, *args, **kwargs):
self.object = self.get_object()
form = self.get_form()
if form.is_valid():
return self.form_valid(form)
else:
return self.form_invalid(form)
def form_valid(self,form):
form.save()
return super(Cd_MixedView, self).form_valid(form)
def form_invalid(self,form):
print("Form Invalid")
return super(Cd_MixedView,self).form_invalid(form)
My template is the following:
<!-- templates/cd_mixed_view.html -->
{% extends 'base.html' %}
{% load static %}
{% block title %} CD Details{% endblock title %}
{% block content %}
<h1>CD Update Track </h1>
{% if cd_info %}
<p>Artist Name: {{ cd_info.artist_name}}
<p>Cd Title: {{ cd_info.cd_title }}
<p>Cd Total Time: {{ cd_info.cd_total_time|time:"H:i:s" }}
<p>Cd Run Time: {{ cd_info.cd_run_time|time:"H:i:s" }}
<p>Cd Remaining Time:
{% if cd_info.cd_run_time_delta > cd_info.cd_total_time_delta %}
(-{{ cd_info.cd_remaining_time|time:"H:i:s" }})
{% else %}
{{ cd_info.cd_remaining_time|time:"H:i:s" }}
{% endif %}
<TABLE BORDER="0" TABLE_LAYOUT="fixed" WIDTH="100%">
<TR BGCOLOR="#B0B0FF">
<TD ALIGN="Center"> Track #</TD>
<TD ALIGN="Center"> Cut Title</TD>
<TD ALIGN="Center">Track Length</TD>
<TD ALIGN="Center" BGCOLOR="#CC99CC">Run Time</TD>
<TD ALIGN="Center" BGCOLOR="#CC99CC">Time Remaining</TD>
</TR>
{% for tracks in cd_info.cd_tracks.all %}
{% if tracks.id != cd_edit.pk %}
<TR>
<TD ALIGN="Left" VALIGN="Top" WIDTH="10"> {{ tracks.track_number }}</TD>
<TD ALIGN="Left" VALIGN="Top"> {{ tracks.track_title }}</TD>
<TD ALIGN="Left" VALIGN="Top">{{ tracks.trk_length_time|time:"H:i:s" }}</TD>
<TD ALIGN="Left" VALIGN="Top">{{ tracks.trk_run_time|time:"H:i:s" }}</TD>
{% if tracks.trk_run_time_delta > cd_info.cd_total_time_delta %}
<TD BGCOLOR="#8DF1BF" ALIGN="Left"> (-{{ tracks.trk_remaining_time|time:"H:i:s" }})</TD>
{% else %}
<TD BGCOLOR="#8DF1BF" ALIGN="Left"> {{ tracks.trk_remaining_time|time:"H:i:s" }}</TD>
{% endif %}
</TR>
{% else %}
<form action="" method="post">
{% csrf_token %}
{{ form }}
<tr>
<td> </td>
<td><input type="submit" value="Update Track Values"></td>
</tr>
</form>
{% if form.errors %}
<p>There were some errors in the information you entered. Please correct the following:</p>
<ul>
{% for field in form %}
{% if field.errors %}
<li>{{ field.label }}:
{{ field.errors|striptags }}</li>
{% endif %}
{% endfor %}
</ul>
{% endif %}
{% endif %}
{% endfor %}
</TABLE>
{% endif %}
{% endblock content %}
The form itself is the following:
class TrackForm(forms.ModelForm):
class Meta:
model = Track
exclude = ('trk_length_time_delta',
'trk_run_time',
'trk_run_time_delta' ,
'trk_remaining_time',
'trk_remaining_time_delta',
)
widgets = {
'cd_id': forms.HiddenInput()
}
I even put a print in the invalid_form function - so it seems to know the form is invalid,
but it just redisplays my form with the previous data.
Any ideas what I am missing here?
Thanks
You use UpdateView yet for some reason you go and redefine most of it's useful methods while not even making any useful or new changes. The main problem is that you override get_context_data and overwrite the form while manually defining it even though the super method has already added the form properly to the context.
Let UpdateView do it's job most of the code you write is not needed as UpdateView already does that:
class Cd_MixedView(UpdateView):
model = Track
template_name = 'cd_mixed_view.html'
form_class = TrackForm
context_object_name = 'cd_edit'
def get_context_data(self, **kwargs):
context = super(Cd_MixedView,self).get_context_data(**kwargs)
cur_track = self.object # no need to get the object again it is already present in `self.object`
# form is already added to context by super method
context['cd_info'] = Cd.objects.get(id=cur_track.cd_id.pk)
return context
# `post`, `form_valid` and `form_invalid` methods were same as super so no need to write yourself

How to check if a field/attribute is null and blank in Django?

My payment attribute has a ForeignKey relation. In my accounts.html, I want to show users if they have paid for their orders. So I was trying it like this:
{% if order.payment is null or blank %}
<td> x</td>
{% else %}
<td> Yes</td>
{% endif %}
But it didn't work out. What is the proper code for this?
My orders.models.py:
class Order(models.Model):
payment = models.ForeignKey(Payment, on_delete=models.SET_NULL, blank= True, null=True)
My accounts.html:
{% for order in user.order_set.all %}
<tbody>
<tr>
<th scope="row">{{ forloop.counter }}</th>
<td>{{ order.order_id }}</td>
{% if order.payment is null or blank %}
<td> x</td>
{% else %}
<td> Yes</td>
{% endif %}
<td>No</td>
</tr>
</tbody>
{% endfor %}
You can simply use {% if not order.payment %}, and I'm pretty sure you can also use {% if not order.payment.exists %}, or {% if order.payment is None %}
blank is not the state of a field. If a field is blank, then it means that no value is required to be passed. The for example a default value is used.
You can check if a value is NULL by checking if the value is None:
{% if order.payment is None %}
…
{% endif %}

Slow Django Model Formset Rendering

I have a Django Model Formset that is rendered with a crispy-forms in a custom table inline formset template. The formset is a list of alerts with entities, logic, and comments. Entity and logic are populated in each form, and the user writes a comment prior to submitting the form.
The formset currently has 140 forms (i.e. 140 records in the Alert model), and it will need to handle more than that number. It takes 4-5 minutes for alerts.html to render. I can use Django-based pagination to limit the queryset and reduce rendering time, but this precludes me from using something like jQuery datatables to paginate and quickly add JS functionality.
I used django-debug-toolbar to review. The queries are definitely funky and duplicated per form (i.e. the query grabbing entity information is duplicated for each form and also includes duplicate where statements). However, it only takes 3-4 seconds for the queries to run, so the problem must be elsewhere. I used Chrom dev tools to record the performance, and the Waiting (TTFB) time is the cause of the performance issue. I understand that this involves server-side processing, which leads me to believe it is a Django issue.
Why is the render taking so long? Is there anything in my code below that could be optimized? I know caching is an option, but I believe this mostly affects query performance which doesn't appear to be the driving issue. Is there any other piece of django-debug-toolbar that might shed more light on timing, outside of just the queries?
EDIT: Based on the comments, I determined the include tags in the table_inline_formset.html template are causing the issue because they are rendering hundreds of the same templates. I created another question here to address this problem: Crispy-Forms Include Tag Causing Many Duplicate Templates.
models.py:
class Logic(models.Model):
logic = models.CharField(max_length=50)
def __str__(self):
return self.logic
class Entity(models.Model):
entity = models.CharField(primary_key=True, max_length=12)
entityType = models.CharField(max_length=10)
entityDescription = models.CharField(max_length=200)
def __str__(self):
return '%s - %s - %s' % (self.entity, self.entityType, self.entityDescription)
class Alert(models.Model):
entity = models.ForeignKey(Entity, on_delete=models.CASCADE, db_column='entity')
logic = models.ForeignKey(Logic, on_delete=models.CASCADE, db_column='logic')
comment = models.CharField(max_length=500)
def __str__(self):
return '%s' % self.entity
forms.py:
AlertFormSet = modelformset_factory(Alert, extra=1, exclude=(), form=AlertForm)
class AlertFormsetHelper(FormHelper):
def __init__(self, *args, **kwargs):
super(AlertFormsetHelper, self).__init__(*args, **kwargs)
self.form_method = 'post'
self.template = 'alerts/table_inline_formset.html'
self.add_input(Submit("submit", "Submit"))
self.layout = Layout(
Field('entity', css_class="input"),
Field('logic', css_class="input"),
Field('comment', css_class="input")
)
views.py:
def alerts(request):
newAlerts = Alert.objects.filter(disposition='')
formset = AlertFormSet(request.POST or None, queryset=newAlerts)
helper = AlertFormsetHelper()
context = {'formset':formset, 'helper':helper}
if request.method == 'POST':
for form in formset:
if form.is_valid():
if form.has_changed():
if form.is_valid():
form.save()
return HttpResponseRedirect('/alerts')
return render(request, 'alerts/alerts.html', context)
table_inline_formset.html:
{% load crispy_forms_tags %}
{% load crispy_forms_utils %}
{% load crispy_forms_field %}
{% specialspaceless %}
{% if formset_tag %}
<form {{ flat_attrs|safe }} method="{{ form_method }}" {% if formset.is_multipart %} enctype="multipart/form-data"{% endif %}>
{% endif %}
{% if formset_method|lower == 'post' and not disable_csrf %}
{% csrf_token %}
{% endif %}
<div>
{{ formset.management_form|crispy }}
</div>
<div class='table-responsive'>
<table{% if form_id %} id="{{ form_id }}_table"{% endif%} class="table table-hover table-sm" id='dispositionTable'>
<thead>
{% if formset.readonly and not formset.queryset.exists %}
{% else %}
<tr>
{% for field in formset.forms.0 %}
{% if field.label and not field|is_checkbox and not field.is_hidden %}
<th for="{{ field.auto_id }}" class="form-control-label {% if field.field.required %}requiredField{% endif %}">
{{ field.label|safe }}{% if field.field.required %}<span class="asteriskField">*</span>{% endif %}
</th>
{% endif %}
{% endfor %}
</tr>
{% endif %}
</thead>
<tbody>
{% for form in formset %}
{% if form_show_errors and not form.is_extra %}
{% include "bootstrap4/errors.html" %}
{% endif %}
<tr>
{% for field in form %}
{% include 'bootstrap4/field.html' with tag="td" form_show_labels=False %}
{% endfor %}
</tr>
{% endfor %}
</tbody>
</table>
</div>
{% include "bootstrap4/inputs.html" %}
{% if formset_tag %}</form>{% endif %}
{% endspecialspaceless %}
alerts.html:
{% crispy formset helper %}

Update table html in django

I am new with Django and I am a little confused trying to update a HTML table populated from the database.
I need to update the table selecting different values from some drop-down list (year, month and provider_type).
This is my table.py:
import django_tables2 as tables
from .models import Proveedor, Estado
class ProveedorTable(tables.Table):
class Meta:
model = Proveedor
fields = ("id_provider", "name", "type", "year", "month")
sequence = ("id_provider", "name", "type", "year", "month")
My views.py
from django.shortcuts import render, render_to_response, RequestContext, HttpResponseRedirect
from django_tables2 import RequestConfig
from .tables import ProveedorTable
from .forms import ProvForm
from .forms import EstadoForm
from .models import Proveedor
from django.contrib import messages
def home(request):
table = ProveedorTable(Proveedor.objects.all())
RequestConfig(request).configure(table)
return render(request,'index.html',{'table': table})
My template index.html
{% load querystring from django_tables2 %}
{% load trans blocktrans from i18n %}
{% load bootstrap_toolkit %}
{% if table.page %}
<div class="table-container">
{% endif %}
{% block table %}
<table class="table table-striped table-condensed table-bordered"{% if table.attrs %} {{ table.attrs.as_html }}{% endif %}>
{% block table.thead %}
<thead>
<tr>
{% for column in table.columns %}
{% if column.orderable %}
<th {{ column.attrs.th.as_html }}>{{ column.header }}</th>
{% else %}
<th {{ column.attrs.th.as_html }}>{{ column.header }}</th>
{% endif %}
{% endfor %}
</tr>
</thead>
{% endblock table.thead %}
{% block table.tbody %}
<tbody>
{% for row in table.page.object_list|default:table.rows %} {# support pagination #}
{% block table.tbody.row %}
<tr class="{% cycle "odd" "even" %}">
{% for column, cell in row.items %}
<td {{ column.attrs.td.as_html }}>{{ cell }}</td>
{% endfor %}
</tr>
{% endblock table.tbody.row %}
{% empty %}
{% if table.empty_text %}
{% block table.tbody.empty_text %}
<tr><td colspan="{{ table.columns|length }}">{{ table.empty_text }}</td></tr>
{% endblock table.tbody.empty_text %}
{% endif %}
{% endfor %}
</tbody>
{% endblock table.tbody %}
{% block table.tfoot %}
<tfoot></tfoot>
{% endblock table.tfoot %}
</table>
{% endblock table %}
{% if table.page %}
{% block pagination %}
<ul class="pagination">
{{ table.page|pagination }}
</ul>
{% endblock pagination %}
{% endif %}
I am confused if I need to use choicefield or an ajax function.
someone can bring me some snippet or a link where I can have a more clear process to implement this functionality
Thanks in advance
Here, <td {{ column.attrs.td.as_html }}>{{ cell }}</td> is where data is being used for displaying data. If you want to do a ajax request here, you have to do it in cell section. For example:
{% for column, cell in row.items %}
{% if column|stringformat:"s" == "some-string" %}
<td {{ column.attrs.td.as_html }} class="ajax-request">{{ cell }}</td>
<!-- or you can use:
<td {{ column.attrs.td.as_html }}><input class="ajax-request" value={{ cell }} type="button (or any other type)"></td>
for choice field, you need to render like
<td {{ column.attrs.td.as_html }}><select id=".ajax-request">
{% for items in cell.values %}
<option value={{ items }}></option>
</select></td>
-->
{% else %}
<td {{ column.attrs.td.as_html }} class="ajax-request">{{ cell }}</td>
{% endif %}
{% endfor %}
<script>
$(document).ready(function(){
$('.ajax-request').change(function(){
var e = document.getElementById("select_dropdown");
var value = e.options[e.selectedIndex].value;
$.ajax({
url: "your-url",
type: "post", // or "get"
data: value,
success: function(data) {
alert(data.result);
}});
});
</script>

Get nth iteration in Django template

I am trying to get the first or nth iteration only in a Django template.
Usually I can iterate through using,
{% for item in pModel %}
{{ item.post }}
{% endfor %}
I need the first iteration but would also like to know how to get the n-th iteration,
{{ pModel.0.post }}` displays nothing and gives no error.
I don't want to iterate through every object in pModel.
I have tried all combinations i.e.
{{ pModel[0][post] }}
{{ pModel.0.[post] }}
{{ pModel[0].post }}
{{ pModel[0][post] }}
{{ pModel.[0][post] }}
{{ pModel.[0].[post] }} etc.
The pModel comes from this view,
def profile(request, id):
pk = id
name = User.objects.all().filter(id=pk)
pModel = reversed(PostModel.objects.all().filter(author = name[0]))
# user_instance = User.objects.all().filter(username = request.user)
return render(request, 'profile.html', {'pModel': pModel, 'current_time': timezone.now()})
The following display nothing,
<strong>{{ pModel.first.post }}</strong>
In the same template I use the pModel which displays correctly so i know that the pModel is working. The complete template,
{% extends 'index.html' %} {% block homepage %}
<div class="post">
{% if pModel %}
<h3>Profile for <strong>{{ pModel.first.post }}</strong></h3>
<p>Last logged in: {{user.last_login|timesince:current_time}} ago on {{ user.last_login }}</p>
<p>Joined {{user.date_joined|timesince:current_time}} ago on {{ user.date_joined }}</p>
{% endif %}
{% if pModel %}
<div class="table-responsive">
<table class='table table-striped table-hover'>
<thead>
<tr>
<th>{{user.username}}'s posts</th>
<th>Topic</th>
<th>Topic Started By</th>
<th>Last Active</th>
<th class="table-cell-center">Views</th>
</tr>
</thead>
<tbody>
{% for item in pModel %}
<tr>
<td>{{ item.post }} uuu {{ pModel.0}}</td>
<td>{{ item.topic.topic }}</td>
<!-- item.topicid.authorid_id -->
<td>{{ item.topic.topicAuthor }}</td>
<td class="icon-nowrap">{{ item.pub_date|timesince:current_time}}</td>
<td class="table-cell-center">{{ item.topic.views }}</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
{% endif %}
</div>
{% endblock %}
Your pModel variable is not a queryset or a list, but a reverse iterator. You cannot access individual elements of an iterator, you can only iterate over the iterator once, exhausting it in the process.
To support access of individual elements, you need to convert pModel to a sequence, such as a list:
pModel = list(reversed(PostModel.objects.filter(author = name[0])))
You can then access the index in your template:
{{ pModel.0.post }}
You can use the forloop.counter0 template variable. For example, to access the n'th element:
{% for item in pModel %}
{% if forloop.counter0 == n %}
{{ item.post }}
{% endif %}
{% endfor %}
You can also use first as a special case:
{{ item.first.post }}

Categories

Resources