how to instance and update multiple row in a table with Django - python

I have a simple table with some rows:
My goal is to instantiate the current value of the quantity and eventually save the new data for all the lines.
At the moment I have a simple view:
#login_required
def compilaDocMultiRow(request,pk):
member = get_object_or_404(testaDoc, pk=pk)
family = corpoDoc.objects.filter(doc=pk)
if request.method == "POST":
form = multiCorpoDocForm(request.POST or None)
if form.is_valid():
reg = form.save(commit=False)
reg.doc_id = member.pk
reg.save()
return HttpResponseRedirect(request.META.get('HTTP_REFERER', '/'))
else:
print(form.errors)
else:
form = multiCorpoDocForm()
return render(request, "compilaDocMultiRow.html", {'family':family, 'member':member, 'form':form})
Is there a way to do this using only Django?
EDIT 1
I was able to instantiate the value with widget tweaks. I am left with the problem of passing the pk of the line to the form and saving the value.
This is the html code:
{% for item in family %}
<tr>
<td>{{ item.item}}</td>
<td>{{ item.desc }}</td>
{% with field=form.qt %}
<td width="15%">{% render_field field class="form-control" placeholder=item.qt %}
</td>
{% endwith %}
<td></td>
<td></td>
</tr>
{% endfor %}

Related

How to add Django Object Models to A Separate List

I am creating an app for a school schedule using Django. Admin users can currently add classes to a list. Student users can view that list but I want to create a button/link that will add that class object to a separate list that will act as their applied classes.
Models.py
class Class(models.Model):
def __str__(self):
return self.name
name = models.CharField(max_length=50)
crn = models.CharField(max_length=5, validators=[RegexValidator(r'^\d{1,10}$')])
grade_mode = models.CharField(max_length=50)
subject = models.CharField(max_length=3)
course_num = models.CharField(max_length=4, validators=[RegexValidator(r'^\d{1,10}$')])
section_num = models.CharField(max_length=3, validators=[RegexValidator(r'^\d{1,10}$')])
credit_hours = models.FloatField(validators=[MinValueValidator(0.0), MaxValueValidator(5.000)])
teacher = models.CharField(max_length=50)
Views.py
#login_required
#dec.student_required
def index(request):
class_list = Class.objects.all().order_by("-name")
# Allow for Posting of New Classes to Schedule.
if request.method == "POST":
form = ClassesForm(request.POST)
if form.is_valid():
form.save()
form = ClassesForm()
messages.success(request, "Class Added to Registry!")
else:
form = ClassesForm()
context_dict = {'classes': class_list,
'form': form}
return render(request, 'index.html', context=context_dict)
Index.HTML Add Button
<table>
<tbody>
{% if classes %}
{% for class in classes %}
<tr>
<td>Class Name: </td>
<td>Subject: </td>
<td>CRN: </td>
<td>Course Number: </td>
<td>Section Number: </td>
<td>Credit Hours: </td>
<td>Teacher: </td>
<td>Grade Mode: </td>
<td>Add Class</td>
</tr>
<tr>
<td>{{ class.name }}</td>
<td>{{ class.subject }}</td>
<td>{{ class.crn }}</td>
<td>{{ class.course_num }}</td>
<td>{{ class.section_num }}</td>
<td>{{ class.credit_hours }}</td>
<td>{{ class.teacher }}</td>
<td>{{ class.grade_mode }}</td>
<td>
<form method="GET">
{% csrf_token %}
<button class="addToSchedule" type="submit" value="add" name="Add Class">
Add Class
</button>
</form>
</td>
</tr>
{% endfor %}
</tbody>
{% else %}
<strong>There are no classes added.</strong>
{% endif %}
</table>
Image on Website.
So far I can print out the models on their own separate list as shown above but I don't know where to go in regards to the button or if a button is even the correct way of doing it. I want to click on a button and that model will be added and saved to the Added Classes section above.

Django Class based Form only updating the final post value

As the title states, I have a table that I'm attempting to update that only updates the final value in the post, from what I understand if I want to update multiple records I must iterate over my request object and update a form instance with the specified ID in my db.
Before submission all records have a price_checked of 0.
and then after - you can see the final value from the post request updates all the records!
postgres table
The code in question that updates my model form instance.
def post(self,request):
if request.method=='POST':
for key in request.POST.getlist('product_id'):
product_update = ProductTable.objects.get(id=key)
form = ProductUpdateForm(request.POST,instance=product_update)
print(form)
if form.is_valid():
form.save()
messages.success(request,message='price checked...')
return redirect('product')
is anyone able to assist? I've been at this point for over 2 weeks.
models/form/view for reference.
models.py
from django.db import models
class ProductTable(models.Model):
id = models.AutoField(
primary_key=True,
editable=False
)
product_name = models.TextField(max_length=255,null=False)
price = models.DecimalField(max_digits=6,decimal_places=2,null=False)
from_date = models.DateTimeField()
to_date = models.DateTimeField(null=True)
price_checked = models.IntegerField(default=0,null=False)
def __str__(self : str) -> str:
return f"{self.product_name} - {self.id} with a price check of {self.price_checked}"
forms.py
from django.forms import ModelForm
from .models import ProductTable
class ProductUpdateForm(ModelForm):
class Meta:
model = ProductTable
fields = ('price_checked',)
views.py
from typing import Any, Dict
from django.contrib import messages
from django.contrib.auth.mixins import LoginRequiredMixin
from django.shortcuts import redirect
from django.views.generic import ListView
from django.db import transaction
from .forms import ProductUpdateForm
from .models import ProductTable
class TableDataView(LoginRequiredMixin,ListView):
model = ProductTable
context_object_name = 'product'
template_name = 'tables/product.html'
def get_context_data(self, **kwargs: Any) -> Dict[str, Any]:
context = super().get_context_data(**kwargs)
context['product'] = ProductTable.objects.filter(pk__in= [4607,4642, 4645])
return context
def post(self,request):
if request.method=='POST':
for key in request.POST.getlist('product_id'):
product_update = ProductTable.objects.get(id=key)
form = ProductUpdateForm(request.POST,instance=product_update)
print(form)
if form.is_valid():
form.save()
messages.success(request,message='price checked...')
return redirect('product')
product.html
{% extends '_base.html' %} {% block content %} {% load crispy_forms_tags %}
<form method="POST" action="">
{% csrf_token %}
{{ form|crispy }}
<div class="container mx-flex pt-5 mb-5">
<table class="table" id="table">
<thead>
<tr>
<th>Product</th>
<th>Price</th>
<th>Date</th>
<th>Input</th>
</tr>
</thead>
<tbody>
{% for data in product %}
<tr>
<th>{{ data.product_name }}</th>
<th>{{ data.price }}</th>
<th>{{ data.from_date }}</th>
<th>
<input type="hidden" name="product_id" value="{{data.id}}" />
<input class="form" name="price_checked" id="priced_checked" type="number" placeholder="{{ data.price_checked }}"/>
<input type="submit" value="OK">
</th>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</form>
{% endblock content %}
request.POST
<QueryDict: {'csrfmiddlewaretoken': ['p0j3e0UbrY1VuFEAnJWopaCICCxOPj8v2OkLRZZiGlUa4YtxGwduD2bAIrm91VKe'], 'product_id': ['4607', '4642', '4645'], 'price_checked': ['1', '2', '3']}>
after read formset documents,I think it should looks like this.
the view python return a formset to template,and in the post function create formset by request.POST
def get_context_data(self, **kwargs: Any) -> Dict[str, Any]:
context = super().get_context_data(**kwargs)
ProductFormSet = modelformset_factory(ProductTable)
context['product_formset'] = ProductFormSet(queryset=ProductTable.objects.filter(pk__in= [4607,4642, 4645]))
return context
def post(self,request):
if request.method=='POST':
ProductFormSet = modelformset_factory(ProductTable)
formset = ProductFormSet(request.POST)
if formset.is_valid():
formset.save()
messages.success(request,message='price checked...')
return redirect('product')
in the template I have mark the changes,the important thing is {{ product_formset.management_form }} in the begin form.and {{ form.id }} of each loop,the each cell data should be {{ form.product_name.value() }} or {{ form.product_name.data }},you can try that
{% extends '_base.html' %} {% block content %} {% load crispy_forms_tags %}
<form method="POST" action="">
{% csrf_token %}
{{ form|crispy }}
<!-- changed -->
{{ product_formset.management_form }}
<div class="container mx-flex pt-5 mb-5">
<table class="table" id="table">
<thead>
<tr>
<th>Product</th>
<th>Price</th>
<th>Date</th>
<th>Input</th>
</tr>
</thead>
<tbody>
<!-- changed -->
{% for form in product_formset %}
{{ form.id }}
<tr>
<td>{{ form.product_name.value() }}</td>
<td>{{ form.price.value() }}</td>
<td>{{ form.from_date.value() }}</td>
<td>
{{ form.price_checked }}
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
<input type="submit" value="submit">
</form>
{% endblock content %}
maybe this will not help you,because I never used the django form things.
I guess you are a precise man who want to follow every django's principle.
But as I know,traditional form has much limitation,some ajax or reactjs has more flexible.
A couple changes to support formsets:
Use the same formset for updating and displaying the objects, but disable the fields you don't want to be updated (make them read-only):
class ProductUpdateForm(ModelForm):
disabled_fields = ['product', 'price', 'date_added', 'date_removed']
class Meta:
model = ProductTable
fields = ('product', 'price', 'date_added', 'date_removed', 'price_checked',)
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
for field in self.disabled_fields:
self.fields[field].disabled = True
Then update the view to use the form for displaying and for updating:
from django.forms import modelformset_factory
class TableDataView(LoginRequiredMixin,ListView):
model = ProductTable
context_object_name = 'product'
template_name = 'tables/product.html'
def get_context_data(self, **kwargs: Any) -> Dict[str, Any]:
context = super().get_context_data(**kwargs)
ProductTableFormset = modelformset_factory(ProductTable, form=ProductUpdateForm,)
context['formset'] = ProductTableFormset(queryset=ProductTable.objects.filter(pk__in=[4607,4642, 4645]))
return context
def post(self,request):
ProductTableFormset = modelformset_factory(ProductTable, form=ProductUpdateForm)
if request.method == 'POST':
formset = ProductTableFormset(request.POST, queryset=ProductTable.objects.filter(pk__in=[4607,4642, 4645]))
if formset.is_valid():
formset.save()
messages.success(request,message='price checked...')
return redirect('product')
And in your template, render the whole formset:
{% extends '_base.html' %}
{% block content %}
{% load crispy_forms_tags %}
<form method="POST" action="">
{% csrf_token %}
{{ form|crispy }}
<div class="container mx-flex pt-5 mb-5">
<table class="table" id="table">
{{ formset }}
</table>
</div>
</form>
{% endblock content %}
EDIT: If you want the simplest change you can do to fix this (so you don't have to work on the styles for the formset), you can do away with the form for the post and update the products as is:
def post(self,request):
if request.method == 'POST':
products_to_update = []
for id, price_checked in zip(request.POST.getlist('product_id'), request.POST.getlist('price_checked')):
product = ProductTable.objects.get(id=id)
product.price_checked = price_checked
products_to_update.append(product)
ProductTable.objects.bulk_update(products_to_update, ['price_checked'])
messages.success(request,message='price checked...')
return redirect('product')

Display part of django model in html table

How to display only some columns of Django model in a HTML template?
And also: how do I perform a function on one of the records? (amount)?
Right now I'm displaying a whole table of model like that:
my models.py
class Tabela(models.Model):
block_id = models.CharField(max_length=64)
timestamp = models.DateTimeField()
type = models.CharField(max_length=32)
link = models.CharField(max_length=64)
link_as_account = models.CharField(max_length=100)
account = models.CharField(max_length=100)
amount = models.CharField(max_length=64)
def __str__(self):
return self.block_id
My views.py
def search_results(request):
model = Tabela
query_addresse = request.GET.get('addressee', None)
query_hash = request.GET.get('hash', None)
if not query_hash and not query_addresse and request.method == 'GET':
return render(request, 'nanosite/index.html', {})
if query_hash and request.method == 'GET':
if query_addresse:
result = Tabela.objects.filter(account=query_addresse, block_id=query_hash)
else:
result = Tabela.objects.filter(block_id=query_hash)
field_names = [f.name for f in model._meta.get_fields()]
data = [[getattr(ins, name) for name in field_names]
for ins in result]
elif query_addresse and request.method == 'GET':
result = Tabela.objects.filter(account=query_addresse)
field_names = [f.name for f in model._meta.get_fields()]
data = [[getattr(ins, name) for name in field_names]
for ins in result]
return render(request, 'nanosite/index.html', {'field_names': field_names, 'data': data})
My index.html
<div id="bottomhalf" class="table-responsive">
<table class="table table-sm table-dark table-hover">
<thead class="thead-light">
{% for head in field_names %}
<th scope="col">{{ head }}</th>
{% endfor %}
</thead>
<tbody>
{% for row in data %}
<tr scope="row">
{% for cell in row %}
<td>{{ cell }}</td>
{% endfor %}
</tr>
{% endfor %}
</tbody>
</table>
</div>
What I'd like to do is display only block_id, timestamp, account and amount in html. I've tried different approaches like using only the result part of views without field_names and data, but of course it didn't work.
My other question is, how can I modify the field amount and perform an operation on it to be displayed in template like amound divided by certain digit with a $ sign before it (for example if amount=1488 to be divided by 124 and displayed as '$12')?
Pass the queryset qs selecting the objects to display to the template and iterate over it to generate your table:
{% for obj in qs %}
<tr>
<td> {{obj.block_id}} </td>
<!-- etc ... -->
</tr>
{% endfor %}
Now, if you also want to pass a variable specifying the names of the fields of the object to tabulate, and in what order, you find out that the Django template engine is by design (!) incapable of doing that. You can either do what you are doing, and generate a list-of-rows in Python which you pass to the Template, or you need a Django custom template tag such as
#register.filter
def attr( obj, name):
return getattr( obj, name, '')
and then you can run an inner loop in your template
<tr>
{% for name in selected_field_names %}
<td> {{obj|attr:name}} </td>
{% endfor %}
</tr>
The answer to the second question, is to define a property on your model to return the field suitably transmogrified:
class Tabela(models.Model):
...
#property
def funny_amount(self):
val = self.amount/12.0
return f'$ {val:.2f}'
and refer to {{obj.funny_amount}} in your template

Sum of objects' prices in Django template

I would like to compute the total of a shopping cart in my template:
This is my template with a table of products.
I tried to use a Generator expression in my cart method but it doesn't work.
Any thoughts?
cart.html
<table>
{% if not cart_list %}
{{ "The cart is empty" }}
{% else %}
<tr>
<th>Name</th>
<th>Price</th>
</tr>
{% for product in cart_list %}
<tr>
<td>{{ product.name }}</td>
<td>${{ product.price }}</td>
</tr>
{% endfor %}
<tr>
<td>{{ Total }}</td>
<td>{{ total_prices }}</td>
</tr>
{% endif %}
</table>
views.py
def cart(request):
if request.method == 'GET':
cart_list = Product.objects.filter(in_cart = True)
total_prices = sum(product.price for product in cart_list)
template_cart = loader.get_template('cart/cart.html')
context = {'cart_list': cart_list}
return HttpResponse(template_cart.render(context, request))
Add your total_prices to context variable, if you want it to be visible in the template.
def cart(request):
if request.method == 'GET':
...
total_prices = sum(product.price for product in cart_list)
context = {
'cart_list': cart_list,
'total_prices': total_prices
}
return HttpResponse(template_cart.render(context, request))
But you can try to use aggregate, something like this.
from django.db.models import Sum
Product.objects.filter(in_cart=True).aggregate(Sum('price'))
# you will have something like this -> 34.35 is example
# {'price__sum': 34.35}

Save Formset in DJANGO

I have a formset from a model, but when I try to save more than one formset I get the error:
KeyError at /auditoria/auditoria_tab/ 'id_control'
My view:
#staff_member_required
def AuditoriaView(request):
class RequiredFormset(BaseFormSet):
def __init__(self,*args,**kwargs):
super(RequiredFormset,self).__init__(*args,**kwargs)
for form in self.forms:
form.empty_permitted = False
auditoriaFormset = formset_factory(AuditoriaForm,max_num=31,formset=RequiredFormset)
if request.method == 'POST':
usuario = request.user
accion = 'Registro nuevo Detalle de Auditoria'
formset = auditoriaFormset(request.POST)
for form in formset.forms:
if formset.is_valid():
obj = form.save(commit=False)
#obj.id_control = control
obj.auditor = request.user
obj.save()
else:
formset = auditoriaFormset()
return render_to_response('audit_auditoria.html',
{'formset':formset},
context_instance=RequestContext(request))
My form:
class AuditoriaForm(forms.ModelForm):
id_control = forms.ModelChoiceField(queryset=Control.objects.all().order_by('nombre'),\
required=True,label='Control', widget=forms.Select(attrs={'class':'form-control'}))
id_analista = UserModelNameChoiceField(queryset=User.objects.filter(is_staff=False),\
required = True, label='Analista', widget=forms.Select(attrs={'class':'form-control'}))
fecha = forms.DateField(widget=DateInputCustom())
id_tipoerror = forms.ModelChoiceField(queryset=TipoError.objects.all(),required=True, label='Tipo Falla', widget=forms.Select(attrs={'class':'form-control'}))
id_sharepoint = forms.IntegerField(label='Sharepoint', widget=forms.NumberInput(attrs={'class':'form-control','style': 'width:80px'}))
id_estado = forms.ModelChoiceField(queryset=Estado.objects.all(),required=True, label='Estado', widget=forms.Select(attrs={'class':'form-control'}))
observaciones = forms.CharField(widget=forms.Textarea(attrs={'rows':5,'cols':30}))
class Meta:
model = DetalleAuditoria
exclude = ('auditor',)
def __init__(self,*args,**kwargs):
super(AuditoriaForm,self).__init__(*args,**kwargs)
self.helper = FormHelper(self)
def clean(self):
cleaned_data = super(AuditoriaForm,self).clean()
idc = cleaned_data['id_control']
fechac = cleaned_data['fecha']
try:
DetalleAuditoria.objects.get(id_control = idc,fecha = fechac )
raise forms.ValidationError('Esta Incidencia ya ha sido registrada')
except DetalleAuditoria.DoesNotExist:
pass
return cleaned_data
Template:
<form id="form_audit" class="form" method="post" action="/auditoria/auditoria_tab/">
<table class="table table-responsive table-condensed table-bordered">
{{ formset.management_form }}
{% for form in formset.forms %}
<thead>
<th class="info"colspan="6">
<label >{{ form.id_control.label|capfirst }} </label>
{{ form.id_control }}
</th>
</thead>
<thead>
<th>{{ form.id_analista.label|capfirst }}</th>
<th>{{ form.id_sharepoint.label|capfirst }}</th>
<th>{{ form.fecha.label|capfirst }}</th>
<th>{{ form.id_tipoerror.label|capfirst }}</th>
<th>{{ form.id_estado.label|capfirst }}</th>
<th>{{ form.observaciones.label|capfirst }}</th>
<thead>
<tbody>
<tr class="{% cycle row1,row2 %} formset_row">
<td>{{ form.id_analista }}</td>
<td>{{ form.id_sharepoint }}</td>
<td>{{ form.fecha }}</td>
<td>{{ form.id_tipoerror }}</td>
<td>{{ form.id_estado }}</td>
<td>{{ form.observaciones }}</td>
</tr>
{% endfor %}
</tbody>
</table>
<input type="submit" class="btn btn-info" value="Registrar">
</form>
{% block extra_js %}
<script src="{{STATIC_URL|default:"/static/"}}admin/js/jquery.formset.js"></script>
<script type="text/javascript">
function renewDatePickers() {
$('.datepicker').datepicker('destroy');
$('.datepicker').datepicker({
format: "yyyy-mm-dd",
startDate: "2014-01-01",
endDate: "2020-01-01",
autoclose: true
});
}
$(renewDatePickers);
$('.formset_row').formset({
addText: 'Agregar Detalle Auditoria',
deleteText: 'Eliminar',
prefix: '{{ formset.prefix }}',
added: renewDatePickers
});
</script>
{% endblock extra_js %}
how can I use the field ID_CONTROL from the first formset in the other formsets, when I want to save the data.
I am not sure if I am using in the right way the formsets?.
Any advice
Thanks in Advance
UPDATE:
I get the error in the "clean" function. When the view try to save the second formset ther error appeare in this line in the:
idc = cleaned_data['id_control']
In the error page I can see the post and I can see for the first formset the id_control value, but for the second formset I see all the fields except id_control.
It seems to me a non sense to put the same control id into the next forms in that way.
Anyway, to solve what you asking for, this should be valid (having different "fecha" value the forms):
id_control=None
if request.method == 'POST':
usuario = request.user
accion = 'Registro nuevo Detalle de Auditoria'
formset = auditoriaFormset(request.POST)
for counter, form in enumerate(formset.forms):
if formset.is_valid():
obj = form.save(commit=False)
if counter>0:
obj.id_control = id_control
obj.auditor = request.user
obj.save()
if counter==0:
id_control=obj.id_control
else:
formset = auditoriaFormset()

Categories