Python/ Django- add a custom column to tables generated by a view - python

I have a page on my Django website, which is displaying a number of tables based on information stored in the database.
The view being used to create the page displaying the tables is defined with:
def current_budget(request, budget_id):
""" View the active provisional/deposit budget """
budget = Budget.objects.select_related('project', 'project__budget_overview').prefetch_related('project__projectroom_set', 'project__budget_versions', 'budget_items').get(id=budget_id)
project = budget.project
# project.projectroom_set.filter(budgetitem__isnull=True, cciitem__isnull=True).delete()
if project.budget_overview.deposit_budget_saved: return HttpResponseRedirect(reverse('costing:combined_budget', args=[project.id]))
#This is now done in the costing_home view
# if not budget:
# Budget.objects.create(project=project, current_marker=1)
if not budget.budget_items.exists() and not project.budget_overview.deposit_budget_saved: init_budget(budget) # Create initial BudgetItem objects as standard
budget_items = budget.budget_items.select_related('budget', 'budget__project', 'project_room', 'project_room__room', 'room')#.order_by('build_type', 'build_type_detail', 'project_room', 'order') # .exclude(build_type=None)
budget_items2 = None #budget.budget_items.filter(build_type=None).order_by('build_type_detail', 'project_room', 'room')
context = {
'project': project,
'budget': budget,
'offset1': -5,
'offset2': -4,
}
try: context['current_budget'] = project.budget_versions.get(current_marker=1) #For option name/date on top of pdfs
except ObjectDoesNotExist: pass
if request.GET.get('version') or project.budget_overview.deposit_budget_saved: #Make the fields all readonly
context['readonly'] = True
context['offset1'] = -7
if request.GET.get('report'): #Schedule of works report uses same data as current budget form
""" Client view of budget. IMPORTANT: Hidden items are not displayed here """
items_grouped = groupby(budget_items.filter(hidden_cost=False), lambda x: x.build_type)
grouped_items = [(x, list(y)) for x, y in items_grouped]
context['grouped_items'] = grouped_items
if request.GET.get('pdf'):
template = get_template('costing/report_schedule_of_works.html')
html = template.render(context)
file = open('test.pdf', "w+b")
pisaStatus = pisa.CreatePDF(html.encode('utf-8'), link_callback=fetch_resources, dest=file,
encoding='utf-8')
file.seek(0)
pdf = file.read()
file.close()
return HttpResponse(pdf, 'application/pdf')
else:
context['webview'] = 1
context['html'] = render_to_string('costing/report_schedule_of_works.html', context)
context['active_tab'] = '3'
return render(request, 'costing/reports_pre_deposit.html', context)
else:
if not context.get('readonly'):
context['skill_day_rate'] = skill_day_rate
context['labour_day_rate'] = labour_day_rate
# Dict with ProjectRoom ids and the total for the room
room_totals = {}
for project_room in project.projectroom_set.all():
room_totals[project_room.id] = sum(item.total_inc_profit for item in budget_items if item.project_room == project_room)
context['room_totals'] = room_totals
item_formset = BudgetItemFormset(queryset=budget_items, form_kwargs={'project': project})
item_form = item_formset.forms[0]
context['field_count'] = len(item_form.visible_fields())
context['ao_field_count'] = len(item_form.visible_fields())
room_choices = project.room_choices
context['formset'] = item_formset
context['widths'] = budget_item_column_widths #First column is add/delete options to allow for forloop count offset
context['options_width'] = options_width #First column is add/delete options to allow for forloop count offset
context['labour_rate'] = labour_day_rate
context['skill_rate'] = skill_day_rate
context['item_code_options'] = ItemCodeForm()
skill_total = int(budget_items.aggregate(Sum('skill_days'))['skill_days__sum'] or 0)
if budget_items2: labour_total = int(budget_items2.aggregate(Sum('labour_days'))['labour_days__sum'])
else: labour_total = 0
return render(request, 'costing/budget_current.html', context)
I now want to add a 'custom' column to these tables, to allow the user to enter their own notes in (i.e. one that will not be displaying data retrieved from the database, but which should add information to each row in the database when it is saved).
How would I do this? I would have thought that I will do it in the view (Python), rather than in the template (HTML), since the view is where the tables are being constructed?
Edit
So, I've added the extra field to the model in models.py for the app:
class Deposit(models.Model):
""" Each Deposit is linked to a Payment, whose is_booking_deposit field = True """
project = models.OneToOneField('projects.Project', null=True, blank=True)
half_paid = models.DateField(null=True, blank=True)
date_received = models.DateField(null=True, blank=True)
amount_exc_vat = models.DecimalField(decimal_places=2, max_digits=12, null=True, blank=True)
invoice_raised = models.DateField(null=True, blank=True)
notes = models.TextField(null=True, blank=True)
# Create a column for 'custom notes' in the tables:
custom_notes = models.TextField(null=True, blank=True) #Add the column to table in tables.py
created = models.DateTimeField(auto_now_add=True)
modified = models.DateTimeField(auto_now=True)
payment = models.OneToOneField(Payment, null=True)
...
The field that I've added to the Deposit model above is the custom_notes one.
I also tried adding the same field to the BudgetItemTable() in tables.py:
class BudgetItemTable(tables.Table):
# Add a column for 'custom notes' to the tables for current budget
custom_notes = tables.Column(accessor='self.custom_notes')
class Meta:
model = BudgetItem
attrs = {"class": "paleblue"}
exclude = ('id')
I've run python manage.py makemigrations & python manage.py migrate myApp, but when I refresh my browser to view this page again, the new column for my table is not displayed- do I need to add it to the view somehow? How would I do this?
Edit
The HTML file for the view that displays the table I want to add a column to has the following structure:
...
{% block page_options_style %}allow-footer{% endblock page_options_style %}
{% block page_options %}
...
{% block report %}
<div id='budget_form' class="col-12" data-pr="{{project.id}}" style="margin-bottom:7em;">
<form class="autosave_form formset num_refresh text-sm" data-view-url="{% url 'costing:save_items' budget.id %}">{% csrf_token %}
{{formset.management_form}}
<div>
<table ...>
<thead class=... >
<tr class=...>
...
<!-- code to get table headings here -->
<th style="width:{{options_width}}">Custom Notes</th>
<!-- I added the 'Custom Notes' heading myself -->
</tr>
</thead>
...
<tbody>
...
<tr id="item_{{forloop.counter}}" class="{% cycle 'odd' 'even' %}">
...
<!-- code add columns and populate rows here -->
<td>
<a class="delete" ... ></a>
<!-- I can see this column with the 'delete' values is the last column in the table on the webpage, so I want to manually add another column after it in the table- I tried doing this by adding the following tags: -->
</td>
<td>
<a class="custom_notes" type="text" value=""></a>
</td>
...
</tr>
</tr>
</tbody>
</table>
...
</tbody>
</table>
</div>
</form>
I tried to add the 'Custom Notes' column to the table with the lines:
<td>
<a class="custom_notes" type="text" value=""></a>
</td>
When I view this page in the browser, I can see the Custom Notes column heading that I've added, displayed above the table, but it is displayed just to the right of where the table ends (I expected the column that I added to the table to be displayed below it).
The table column for 'custom notes' that I have tried adding is not displayed in the table at all...
Why is this? How can I get the new column to be displayed in the table on the webpage? I've added the corresponding field to the model in models.py, so although there won't be any data in this field for any of the items in the database at the moment, when the user enters data in this field in the table, there is somewhere for those values to be stored in the model in the database...
Edit
I managed to add this additional field to the table by appending the following HTML to the table, just inside the last two </tr> tags, inside </tbody></table>:
<td>
<a class="custom_notes" type="text" value=""></a>
<input id="budget_notes" type="text" width="100" value="">
</td>
But for some reason, the text input field that I have added is very narrow- you can't see more than a few characters of text at a time when typing into it. I tried specifying the width of the field using width="100", as shown above, but this doesn't appear to have made any difference. How can I force the cells in this column to have a set width?

Related

Display crypto price in a table using django and cryptocompare

I'm trying to make a simple webpage in django where I can display the price of some cryptocurrencies. The user stores in the database the name of the crypto and its initial price. Then I want to fill a table with the ticker and the initial price taken from the database and the current price of each crypto taken with cryptocompare.
This is where the problem begin: the value of 'current price' is taken using cryptocompare, but how can I dinamycally display the value of the current price of each crypto in the table? Maybe the problem is easy to solve but I'm really new and I'm stuck, thanks in advance!
If you need more information ask me!
This is how the table in the index look like. I need to fill the last column with the daily price of the crypto
models.py
from django.db import models
# Create your models here.
class crypto(models.Model):
ticker = models.CharField(max_length=200)
initial_price = models.FloatField()
def __str__(self):
return self.ticker
views.py
from .models import crypto
def index_view(request):
chart = FuncAnimation(plt.gcf(), animate, interval=1000)
ticker_list = crypto.objects.all()
return render(request, 'cryptochart/index.html', {'chart': chart, 'ticker_list': ticker_list})
and last, in a file called utils.py I wrote the functions to get the price
import cryptocompare
#get price of cryptocurrency
def get_crypto_price(cryptocurrency,currency):
return cryptocompare.get_price(cryptocurrency,currency=currency)[cryptocurrency][currency]
#get fullname of the cryptocurrency
def get_crypto_name(cryptocurrency):
return cryptocompare.get_coin_list()[cryptocurrency]['FullName']
index.html
<table style="width: 100%;">
<tr>
<th>Ticker</th>
<th>Prezzo Iniziale</th>
<th>Prezzo Giornaliero</th>
</tr>
{% if ticker_list %}
{% for ticker in ticker_list%}
<tr>
<td>{{ticker.ticker}}</td>
<td>{{ticker.initial_price}}</td>
<td>{{???}}</td>
</tr>
{%endfor%}
{% endif %}
</table>

django Insert or Update record

the insert works but multiple data enters, when two data are inserted when I try to update, it does not update the record
I just want that when the data is already have record in the database, just update. if not, it will insert
def GroupOfProduct(request):
global productOrderList
relatedid_id = request.POST.get("relatedid")
groups = ProductRelatedGroup(id=id)
productOrderList=[]
try:
for products_id in request.POST.getlist("product"):
products = Product(id=products_id)
insert_update_groupofproduct = ProductRelatedGroupAndProduct(
product = products
)
insert_update_groupofproduct.save()
except ProductRelatedGroupAndProduct.DoesNotExist:
for products_id in request.GET.getlist("relatedid"):
products = Product(id=products_id)
insert_update_groupofproduct = ProductRelatedGroupAndProduct.objects.get(id=products)
insert_update_groupofproduct.product = products
insert_update_groupofproduct.save()
return redirect(relatedgroup)
this is my models.py
class Product(models.Model):
product = models.CharField(max_length=500)
class ProductRelatedGroup(models.Model):
category = models.CharField(max_length=500, blank=True)
class ProductRelatedGroupAndProduct(models.Model):
productrelatedgroup = models.ForeignKey(ProductRelatedGroup,on_delete=models.SET_NULL, null=True, blank=True,verbose_name="Product Related Group")
product = models.ForeignKey(Product,on_delete=models.SET_NULL, null=True, blank=True,verbose_name="Product")
UPDATE
I tried this, the insert works fine, but the update does not work
def GroupOfProduct(request):
global productOrderList
groups = ProductRelatedGroup(id=id)
idproduct = request.POST.get('relatedid')
if ProductRelatedGroupAndProduct.objects.filter(id=idproduct).exists():
print("update")
for products_id in request.GET.getlist("relatedid"):
products = Product(id=products_id)
insert_update_groupofproduct = ProductRelatedGroupAndProduct.objects.get(id=products)
insert_update_groupofproduct.product = products
insert_update_groupofproduct.save()
return redirect(relatedgroup)
else:
productOrderList = []
for isa in request.POST.getlist('relatedid'):
productOrderList.append(isa)
i = 0
for i in productOrderList:
for products_id in request.POST.getlist("product"):
products = Product(id=products_id)
insert_update_groupofproduct = ProductRelatedGroupAndProduct(
productrelatedgroup=groups,
product=products
)
insert_update_groupofproduct.save()
return redirect(relatedgroup)
return redirect(relatedgroup)
FLOW OF MY PROGRAM (with picture)
when the admin-user Insert data just (like this)
the batch insert work perfectly fine
and when i tried to update (batch update)
only one data updated
and when i tried to insert again (just like this)
no result
In Insert
<QueryDict: {'csrfmiddlewaretoken': ['F3evgRJwNw4p5XCOVE0qeFhP3gmGG5ay4GBbpoZQg3P5l6TNXHY7KN2lD56s6NCU'], 'relatedid': ['200', '201']}>
This is my html,
{% for relatedgroups in relatedgroups %}
<input type="hidden" name="relatedid" value="{{relatedgroups.id}}">
{% endfor %}
<fieldset class="module aligned ">
<div class="form-row field-user_permissions">
<div>
<div class="related-widget-wrapper">
<select name="product" id="id_user_permissions" multiple class="selectfilter" data-field-name="Product" data-is-stacked="0">
{% for relatedgroups in relatedgroups %}
<option value="{{relatedgroups.product.id}}" selected>{{relatedgroups.product}}</option>
{% endfor %}
{% for product in products %}
<option value="{{product.id}}">{{product.id}}-{{product}}</option>
{% endfor %}
</select>
</div>
</div>
</div>
</fieldset>
when the data is selected or already have data in the database it will shows in Chosen Product box
Please use update_or_create method. This method if a data is exist then updated the details else newly inserted.
Reference:
https://www.kite.com/python/docs/django.db.models.QuerySet.update_or_create
https://djangosnippets.org/snippets/1114/
def GroupOfProduct(request):
group_id = request.POST.get('group')
groups = ProductRelatedGroup(id=group_id)
idproduct = request.POST.get('product')
for products_id in request.POST.getlist("product"):
products = Product(id=products_id)
ProductRelatedGroupAndProduct.objects.update_or_create(id=idproduct,defaults={'product':products,'productrelatedgroup':groups})
return redirect(relatedgroup)
We need to delete all the records first from ProductRelatedGroupAndProduct using the related_ids (product ids which were already selected) and then enter again into ProductRelatedGroupAndProduct using product_ids (product ids which are selected).
Why we need to delete ?
In a situation where you want to remove a product from the selected list, an update would not delete the relation. In-order to remove that product from the selected list we need to delete that relation (not the Product).
def GroupOfProduct(request):
related_ids = request.POST.getlist('relatedid')
product_ids = request.POST.getlist("product")
# delete the existing ProductRelatedGroupAndProduct entries
for product_id in related_ids:
product = Product.objects.filter(id=product_id).first()
obj = ProductRelatedGroupAndProduct.objects.get(product = product)
obj.delete()
# create new records in ProductRelatedGroupAndProduct with the selected product_ids
for product_id in product_ids:
product = Product.objects.filter(id=product_id).first()
obj = ProductRelatedGroupAndProduct(
product = product,
)
obj.save()

Show model fields with Table in Django Admin's Change Page

Problem:
I have a project model and its inline models lets say InlineModel_1 and InlineModel_2.
I want the Project's Add & Change pages can create or edit the Project's fields and InlineModel_1.
Besides, I want an extra Change Page specific for review the Project info and edit InlineModel_2
Django 2.1.7 & Python 3.7
I'm new to Django, so pls advice if anything incorrect, thanks!
Solution Result:
In Changelist Page
Add a link to Original Chang Page and use original link to go to the customized Change Page
Original Change Page, similar to the Add page
New Customized Change Page for inline adding use
Procedure
Setup ProjectAdmin and add a link field to Original Change Page
class ProjectAdmin(BaseAdmin):
list_display = ('id', 'name', 'constructor', 'client',
'total_amount', 'edit_tag',
readonly_fields = ('total_amount', 'order_form', 'project_info_table')
# for add and edit use
default_fields = (
'name',
'constructor',
'client',
'total_amount',
)
# for review and add inline use
default_fieldset = (
('工程项目信息', {
'fields': ('project_info_table',)
}),
('报价单', {
'classes': ('collapse',),
'fields': ('order_form',),
}),
)
# a link to orginal Change Page
def edit_tag(self, obj):
return mark_safe(
f'Edit'
)
edit_tag.short_description = 'Edit'
Add method to ModelAdmin to generate Table Html with field values
class Project(BaseModel):
# ....
# generate table1 html
def project_info_table(self):
# just one row, use thead only instead of a whole table
table = """
<table style="width:100%">
<thead>
<tr>
<th>Project name</th>
<th>Constructor</th>
<th>Client</th>
<th>Total amount</th>
</tr>
</thead>
<tbody>
<tr>
<td>{}</td>
<td>{}</td>
<td>{}</td>
<td>{}</td>
</tr>
</tbody>
</table>
"""
return format_html(
table,
self.name,
self.constructor,
self.client,
self.total_amount()
)
project_info_table.short_description = '工程信息表'
# generate table2 html
def order_form(self):
table = """
<table style="width:100%">
<thead>
<tr>
<th>标号</th>
<th>类型</th>
<th>泵送</th>
<th>自卸</th>
</tr>
</thead>
<tbody>{}{}</tbody>
</table>
"""
return format_html(
table,
# format_html_join can repeat the row with the values
# from a iterator
format_html_join(
'\n', "<tr><td>{}</td><td>{}</td><td>{}</td><td>{}</td></tr>",
((
p.grade, p.type, p.pumpcrete, p.dumpcrete
) for p in self.products.all().order_by(
'-grade', 'type', 'pumpcrete'
))
),
format_html("<tr><td>泵送方数低于:{}</td><td>加出泵费:{}</td><td>方数低于:{}</td><td>加空载费:{}</td></tr>",
self.min_cube_pump,
self.price_pump,
self.min_cube_extra,
self.price_extra
)
)
order_form.short_description = '合同报价单'
# another readonly field
def total_amount(self):
result = self.transaction_set.filter(
is_deleted=False
).aggregate(
Sum('total_price')
)['total_price__sum'] or 0
return '¥ {:,}'.format(result)
If want to keep the original Change Page and the above Inline Change Page, override change_view method in admin to show
class ProjectAdmin(BaseAdmin):
....
# different fields and inline according to GET method's parameter
# need to reset fields and fieldset in different view
def change_view(self, request, object_id, form_url='',
extra_context=None):
if request.GET.get('edit', False):
# Show original Change Page
# Don't use fieldset cause it's only for customized page
self.fieldsets = None
self.fields = self.default_fields
# able to user different inline for different page
self.inlines = [DocumentInline]
else:
# Show customized page
# don't sue fields use fieldset instead
self.fields = None
self.fieldsets = self.default_fieldset
# use another inline for customized page
self.inlines = [TransactionInline]
return super().change_view(request, object_id,
extra_context=extra_context)
def add_view(self, request, form_url='', extra_context=None):
self.fieldsets = None
self.fields = self.default_fields
self.inlines = [DocumentInline]
return super(ProjectAdmin, self).add_view(request)

Django hide field from forms and add automatically field

I want to add to the cart the actual product I'm in (product_detail.html).
So in the product_unit, is just needed to specify the quantity of the product.
Anyway I can't make the unit_product, automatically add the actual product I'm in.
forms.py
class Product_unitForm(forms.ModelForm):
class Meta:
model = Product_unit
fields = [
'product',
'quantity',
]
widgets = {'product': forms.HiddenInput()}
I hide the product from the template, because it is just the actual product, no need to specify.
views.py
def product_detail(request, id_category=None,id_product=None):
actual_product = Product.objects.get(id = id_product)
#Has an actual customer
#FORM
form_product_unit = Product_unitForm(request.POST or None)
form_product_unit.fields['product'] = actual_product # I try to add the product this way
if form_product_unit.is_valid():
instance_product_unit = form.save(commit=False)
instance_product_unit.product.save()
last_order = Order.objects.last()
is_buying = False
if(last_order.status == "en curso"):
is_buying = True
context = {
"Product" : actual_product,
"Is_buying" : is_buying,
#FORMS
"form_product_unit" : form_product_unit,
}
return render(request, "shopping/product_detail.html", context)
I want to manually from the views, add the product field of product_unit to the actual product it has (actual_product)
template
<img src="{{Product.image.url}}"/>
<h1>{{Product.title}}</h1>
<form method="POST" action="">{% csrf_token %}
{{ form_product_unit.as_p }}
<input type="submit" value="Add" />
</form>
In your views.py file I think you just need to make two changes
def product_detail(request, id_category=None,id_product=None):
actual_product = Product.objects.get(id = id_product)
form_product_unit = Product_unitForm(data=request.POST or None,
initial={'product': actual_product})
And also remove the line form_product_unit.fields['product'] = actual_product. You might need to play around with the initial dictionary a bit to get it to bind the correct value to the field but that's the general idea. The related section in the docs is https://docs.djangoproject.com/en/1.9/ref/forms/api/#dynamic-initial-values

Django - Rating System View & Template

I have a content that I'd like to rate on multiple criteria.
Imagine this kind of model:
class Content(models.Model):
name = models.CharField(max_length=50)
class Criterion(models.Model):
name = models.CharField(max_length=50)
content = models.ForeignKey(Content)
class ContRate(models.Model):
user = models.ForeignKey(User, help_text="Who rated ?")
crit = models.ForeignKey(Criterion)
rate = models.DecimalField()
The user has a page displaying the content.
From this page, he can also rate the content on the criteria set
The rating will be done with Ajax.
Now I'm trying to implement the view & the template
view.py
#...
def viewcont(request, content_id):
"""The user can view a content & rate it"""
content = get_object_or_404(Content, pk=content_id)
RateFormSet = modelformset_factory(ContRate)
formset = RateFormSet(queryset=ContRate.objects.filter(content=content, user=request.user))
objs = {
'content': content,
'forms': formset,
}
return render_to_response('content/content_detail.html', objs
, context_instance=RequestContext(request)
)
#...
content_detail.html
<!-- ... -->
<div id="rating">
<ul>
{% for crit in content.crit_set.all %}
<li>
{{ crit }}
<div class="rateit"
data-rateit-value="the_actual_rating_if_already_there"
data-rateit-ispreset="true"
crit-id="{{ crit.id }}"></div>
</li>
{% endfor %}
</ul>
</div>
<!-- ... -->
Now how can I use the forms formset to display the actual rates ?
And how can I draw an empty form to be posted by Ajax from any clicked star ?
(I know the javascript/jQuery part)
Not sure what the point of the formset is here. The rates are all available via the criteria object, using the reverse foreign key to ContRate in exactly the same way as you've done from Criteria to Content.
To make this as efficient as possible, you probably want to get the relevant ratings in the view and bring them together into a single datastructure:
content = get_object_or_404(Content, pk=content_id)
criteria = content.criteria_set.all()
user_ratings = ContRate.objects.filter(content=content, user=request.user)
ratings_dict = dict((c.crit_id, c.rate) for c in user_ratings)
for crit in criteria:
crit.user_rating = ratings_dict.get(crit.id)
Now you can pass criteria directly to your template, and there you can iterate through it to show the user_rating for each one.
(Final point: "criteria" is plural, the singular is "criterion". :-)

Categories

Resources