Django-tables2 send parameters to custom table template - django

I'm trying to use a custom table template to embed the django-filter fields on my table. So I copied the django-tables2 bootstrap.html template in a new file custom_table.html. Then I added it the following code in the thead section:
{% if filter %}
<tr>
{% for filter_field in filter.form.fields %}
<td>
{{ filter_field }}
</td>
{% endfor %}
<td>
<button class="login100-form-btn" type="submit">Filter</button>
</td>
</tr>
{% endif %}
So the problem is : how can I send the filter to the table template?

I resolved this issue. I have overridden the get_context_data function of my view:
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
table = self.get_table(**self.get_table_kwargs())
table.filter = self.filterset
context[self.get_context_table_name(table)] = table
return context
In this way, I can use the filter in my custom table template by the following code:
{% if table.filter %}
<tr>
<form action="" method="get" class="form-inline">
{% csrf_token %}
{% for field_form in table.filter.form %}
<th>
{{field_form}}
</th>
{% endfor %}
<th>
<button class="btn" type="submit">Filter</button>
</th>
</form>
</tr>
{% endif %}

You can send the query paramters to the server, and construct the table using filtered_queryset. For example:
#views.py
def your_view(request):
filter = ModelFilter(request.GET, queryset=Model.objects.all())
filtered_queryset = filter.qs
# pass filtered_queryset to your table
table = SimpleTable(filtered_queryset)
return render(request, 'table.html', {'table': table})

Related

Django object update using htmx and SingleObjectMixin

I'm using htmx for the first time. I have a table where each cell is a grade object. Previous to htmx I made each cell a link to an UpdateView for the object. I am now trying to have the user modify the the object's score field directly in the table using htmx. I'm sure I'm doing several things wrong.
My page loads as expected and the table is displayed as expected. when I type in a cell, I get an error Forbidden 403. CSRF Verification Failed.
The purpose of this post/question is to figure out how to get past this 403 error. Having said that, if it turns out that I'm going down the completely wrong path with using a SingleObjectMixin, please let me know.
View
class GradeChange(SingleObjectMixin, View):
""" view to handle htmx grade change"""
model = Grade
def post(self, request, *args, **kwargs):
grade = self.get_object()
assessment = Assessment.objects.get(grade=grade.pk)
print(assessment)
classroom = Classroom.objects.get(classroom=grade.cblock)
print(classroom)
if grade.score == "EXT" or grade.score=="APP" or grade.score=="DEV" or grade.score=="BEG":
grade.save()
return HttpResponse("S")
else:
return HttpResponse("")
template
<table class="table table-bordered table-sm">
<thead>
<tr>
<th class="col-3" scope="col">Students</th>
{% for obj in objective_list %}
<th class="col-2" scope="col">{{ obj.objective_name }}</th>
{% endfor %}
<th scope="col">Comments</th>
</tr>
</thead>
<tbody>
<form action="" method="post" class="form-group">
{% csrf_token %}
{% for student in student_list %}
<tr>
<td >{{ student.student_first }} {{ student.student_last }}</td>
{% for g in grade_list %}
{% if g.student.id == student.id %}
<td>
<input type="text" hx-post="{% url 'gradebook:grade-change' g.pk %}" hx-target="" hx-trigger="keyup delay:2s" class="form-control score" title={{ g.score }} name="score" id="input-{{ forloop.counter0 }}" placeholder={{ g.score }} required>
</td>
{% endif %}
{% endfor %}
<td>
{% for comms in comment_list %}
{% if comms.student == student %}
<a class="grade-comment" href="{% url 'gradebook:addcomment' comms.pk assess_pk class_pk %}">{{ comms.comment|truncatewords:10 }}</a>
{% endif %}
{% endfor %}
</td>
</tr>
{% endfor %}
</form>
</tbody>
</table>
EDIT - a solution
I came across this, which works.
<script>
document.body.addEventListener('htmx:configRequest', (event) => {
event.detail.headers['X-CSRFToken'] = '{{ csrf_token }}';
});
</script>
The CSRF token must be included in the HTMX POST request. Currently you include it only in the regular form, but the HTMX request is initiated from a child input element where the token is not present. So just include the following hx-header attribute to a parent element like the <form> or event the <body> is a good candidate:
<body hx-headers='{"X-CSRFToken": "{{ csrf_token }}"}'>

How do I use django_tables2 with a filter?

I'm displaying data on a webpage, and I would like to migrate this code to use the table I created in tables.py. I can't figure out how to do it without breaking the filter.
views.py
def PlatListView(request):
queryset = Plat.objects.all().values('id', 'description','status', 'phase__number','phase','schedule_found').annotate(lot_count=Sum('phase__lot_count')).order_by('description')
f = PlatFilter(request.GET, queryset=queryset)
return render(request, 'blog/filtertable2.html', {'filter': f})
filters.py
class PlatFilter(django_filters.FilterSet):
community = ModelChoiceFilter(queryset=Community.objects.all())
tables.py
import django_tables2 as tables
class PlatTable(tables.Table):
id = tables.Column()
description = tables.Column()
status = tables.Column()
phase__number = tables.Column()
lot_count = tables.Column()
schedule_found = tables.Column()
class Meta:
ordering = 'description'
#model = Plat
filtertable2.html
{% extends "blog/base.html" %}
{% block content %}
{% load bootstrap4 %}
<form method="get">
{{ filter.form.as_p }}
<input type="submit" />
</form>
<table>
<tr>
<th>Description</th>
<th>Status</th>
</tr>
{% for obj in filter.qs %}
<tr>
<td> {{ obj.description }}</td>
<td>{{ obj.status }}</td>
</tr>
{% endfor %}
</table>
{% endblock content %}
In your view you construct the table with the data from the filter:
def PlatListView(request):
queryset = Plat.objects.annotate(
lot_count=Sum('phase__lot_count')
).order_by('description')
f = PlatFilter(request.GET, queryset=queryset)
table = PlatTable(data=f.qs)
return render(request, 'blog/filtertable2.html', {'filter': f, 'table': table})
You can then render the table with:
{% extends "blog/base.html" %}
{% block content %}
{% load bootstrap4 %}
{% load render_table from django_tables2 %}
<form method="get">
{{ filter.form.as_p }}
<input type="submit" />
</form>
{% render_table table %}
{% endblock content %}
I do not see any particular reason for using Values QuerySet, ie values(). You can simply annotate the value with queryset:
def plat_list_view(request): # using snake_case when defining a method name.
queryset = Plat.objects.annotate(lot_count=Sum('phase__lot_count')).order_by('description')
f = PlatFilter(request.GET, queryset=queryset)
return render(request, 'blog/filtertable2.html', {'filter': f})

Flask app not displaying list as expected

I have an app that scrapes a website using beautiful soup. It returns the data in a table for me. I implemented a search bar using wtforms and a sqlite3 query.
Web page sample
This returns the data I want, but not how I want it. As soon as I submit the search it loads a list on a page instead of on the index.
the results after search
This is my flask app (part of):
#app.route('/', methods=('GET', 'POST'))
def index():
products = get_products()
form = SearchForm()
if form.validate_on_submit():
item = '%' + form.searchFor.data + '%'
c.execute("SELECT * FROM supplementInfo WHERE (name) LIKE (?)", (item,))
search_results = c.fetchall()
return str(search_results)
return render_template('index.html', products=products, form=form)
And this is the html:
<div col-12 class="wrap">
<div class="search">
<form method="POST" action="/">
{{ form.csrf_token }}
{{ form.searchFor(class_="searchTerm") }}
<button type="submit" class="searchButton">
<i class="icon-search"></i>
</button>
</form>
</div>
</div>
<div class="infoTable">
<table>
{% for row in products %}
<tr>
<td>{{ row[0] }}</td>
<td> {{ row[1] }}</td>
<td> {{ row[2] }}</td>
<td> {{ row[3] }}</td>
</tr>
{% endfor %}
</table>
</div>
</center>
<div>
<table>
{% for item in search%}
<tr>
<td>{{ item.name}}</td>
</tr>
{% endfor %}
</table>
</div>
{% endblock %}
Ok, I figured it out. I had to change the app to read as such:
#app.route('/', methods=('GET', 'POST'))
def index():
products = get_products()
form = SearchForm()
if form.validate_on_submit():
item = '%' + form.searchFor.data + '%'
c.execute("SELECT * FROM supplementInfo WHERE (name) LIKE (?)", (item,))
search_results = c.fetchall()
return render_template('index.html', products=products, form=form, search_results=search_results)
return render_template('index.html', products=products, form=form)
I realized I was returning the list instead of returning the template and passing it the list.

Get ID of a ManyToManyField on ModelForm

I have a particular question envolving the editing of a model with a m2m field that i can't seem to find an answer anywhere. So i have a model, Equipment with an m2m relation to Tests.
models.py:
class Equipment(models.Model):
...
tests = models.ManyToManyField(Test, blank=True)
def __str__(self):
return '%s %s' % (self.manufacturer, self.model)
pass
forms.py:
class EquipmentForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
modality = kwargs.pop('modality', None)
super (EquipmentForm, self).__init__(*args, **kwargs)
self.fields['tests'].widget = forms.CheckboxSelectMultiple()
self.fields['tests'].queryset = Test.objects.filter(modality=modality).order_by('group', 'number')
class Meta:
model = Equipment
fields = ['tests']
html:
...
<tbody>
{% for f in form.tests %}
<tr>
<td>{{ f }}</td>
<td>
<a data-toggle="modal" href="{% url 'app:modal' equipament_id=equipament_id test_id=f.id %}" data-target="#tests">See</a>
</td>
</tr>
{% endfor %}
</tbody>
As you can see i am trying to open a modal for each test field in the form so the user can see some information related to that test. This is where i am having trouble because i cant seem to get that f.id to pass it to a view that renders the modal dialog. Any ideas of how to do that? Please help.
First Attempt
{% for field in form.visible_fields %}
{% if field.name == "tests" %}
<table>
{% for choice in field.queryset %}
<tr>
<td><input value="{{choice.id}}" type="radio" name="tests" />{{choice.description}}</td>
</tr>
{% endfor %}
</table>
{% endif %}
{% endfor %}
This doesn't work because of the queryset. From the documentation it's not supported for a ModelMultipleChoiceField.
Second Attempt
<table>
<tbody>
{% for pk, choice in form.tests.field.choices %}
<tr>
<td>{{ choice }}</td>
<td><a data-toggle="modal" href="{% url 'SFM:modal' equipment_id=equipment_id test_id=pk %}" data-target="#tests">Ver</a></td>
</tr>
{% endfor %}
</tbody>
</table>
With this approach i am able to pass pk to the view and load the corresponding modal but i lost my form checkbox. If i manually render it i am not able to save any checkbox. This tells me that it is possible to pass the pk to view but maybe it should be acessible by querying the field form and not only in the choice. I am running out of ideas.

Django dynamically filter list in template

I have a page which lists all of the athletes that a certain coach has. Coaches, however, can have multiple teams and I am trying to allow them to select a team from a dropdown at the top of the page and dynamically filter the list of athletes to only show those on the selected team.
My template:
<table class='table'>
<tr>
<td><h3>Team</h3></td>
<td><h3>First Name</h3></td>
<td><h3>Last Name</h3></td>
<td><h3>Email</h3></td>
</tr>
{% for athlete in athletes %}
{% if not athlete.coach_ind %}
<tr><td>
{% for team in athlete.team.all %}
{{ team.school }} {{ team.mascot }} {{ team.sport }}
{% endfor %}
</td>
<td>{{ athlete.user.first_name }}</td>
<td>{{ athlete.user.last_name }}</td>
<td>{{ athlete.user.email }}</td>
</tr>
{% endif %}
{% endfor %}
</table>
My view:
teams_list = request.user.profile.team.all()
athletes = UserProfile.objects.filter(team__in=teams_list).order_by('team','user__last_name')
I am able to successfully get the correct list of all athletes and their information, I'm just not sure how to create a dynamic filter to show only by team.
You can use django-filter for it https://github.com/alex/django-filter.
Example from documentation:
Model
class Product(models.Model):
name = models.CharField(max_length=255)
manufacturer = models.ForeignKey(Manufacturer)
Filter
class ProductFilter(django_filters.FilterSet):
class Meta:
model = Product
fields = ['manufacturer']
View
def product_list(request):
f = ProductFilter(request.GET, queryset=Product.objects.all())
return render_to_response('my_app/template.html', {'filter': f})
Template
{% block content %}
<form action="" method="get">
{{ filter.form.as_p }}
<input type="submit" />
</form>
{% for obj in filter %}
{{ obj.name }}<br />
{% endfor %}
{% endblock %}
Another app to those for this type of "chained" selection is selectable (http://django-selectable.readthedocs.io/en/latest/advanced.html#chained-selection). You have to put some additional javascript to the template, though.