django-tables2 permissions in TemplateColumn - django

I feel like I have read about this a hundred times but I still can't figure out how to use permissions within a django-tables2 TemplateColumn.
My goal is to be able to render buttons in a column based on permissions that a user may have or may not have on a given model. That does not sound complicated to me and from what I have read I should be able to use something like {% if perms.myapp.delete_mymodel %} to achieve what I'd like to do.
Here is the code I'm trying to get to work as I expect:
import django_tables2 as tables
MY_MODEL_ACTIONS = """
{% if perms.myapp.change_mymodel %}
<i class="fas fa-edit"></i>
{% endif %}
{% if perms.myapp.delete_mymodel %}
<i class="fas fa-trash"></i>
{% endif %}
"""
class MyModelTable(tables.Table):
# some columns
actions = tables.TemplateColumn(
verbose_name="",
template_code=MY_MODEL_ACTIONS,
)
class Meta(BaseTable.Meta):
model = MyModel
fields = (
# some columns
"actions",
)
When rendering the table no issues are triggered but the column just do not display any buttons (yes I do have the permissions for them to show up). Removing the {% if … %} clauses, thus removing the permission checks, allows the buttons to be seen of course.

The issue was a bit tricky. I defined my own template to render the table and did not used the {% render_table table %} tag inside it. Due to this the context was not reachable from the TemplateColumn code.
To fix this, I changed my template a bit and move my table rendering custom code to another template file. After that I used the render_table tag like this {% render_table table 'includes/table.html' %}
After this, the code that I mentioned above in the column works just fine, permissions are honored like expected.

What adds perms to your context?TemplateColumns do not have the same context as the template {{ render_table table }} is called from, so you must be a little more explicit.
The documentation for render_table mentions it will attach the context of the calling template to table.context, so this should fix your problem:
MY_MODEL_ACTIONS = """
{% if table.context.perms.myapp.change_mymodel %}
<i class="fas fa-edit"></i>
{% endif %}
{% if table.context.perms.myapp.delete_mymodel %}
<i class="fas fa-trash"></i>
{% endif %}
"""

Related

Get a random object of a Wagtail page model but not the current one

So I have this template context processor:
from cases.models import CasePage
def random_case(request):
case = CasePage.objects.live().order_by('?')
return {'random_case': case}
And in the template I do this:
{% for entry in random_case %}
{% if request.get_full_path != entry.get_url %}
{% if forloop.first %}
<a class="ajax-link project-next" href="{{ entry.get_url }}">
<div class="nav-project-title">{{ entry.title }}</div>
<div class="nav-title">next</div>
</a>
{% endif %}
{% endif %}
{% endfor %}
And this works but the problem is sometimes the object is the same as the page so nothing is displayed. It would be great if that one would be skipped in favour of the next entry. And it's also too much logic in the template for me. What would be the best way to move this logic into the context processor and make it work?
Make random_case a method of CasePage, and filter out the case with an ID equal to self.
class CasePage(Page):
# ...
def random_case(self):
return CasePage.objects.live().exclude(id=self.id).order_by('?').first()
You can then refer to this method within your template as page.random_case - bear in mind that a new random choice will be made on each call, so you probably want something like {% with page.random_case as case %}.

Django dynamic template variable inside if clause

Here's what I'm trying to achieve in "pseudo code":
{% for page in pages %}
{% if 'can_access_page_{{page.name}}' in perms %}
<li>
{{ page.name }}
</li>
{% endif %}
{% endfor %}
How to do this? Permission names I can customize — but still can't figure out this one.
Simplest way is to slightly abuse Django's existing add template filter (intended for numbers but works for strings), as in this answer:
https://stackoverflow.com/a/4524851/202168
You'll need a custom filter. Something like:
#register.filter
def check_page_perms(page, perms):
return 'can_access_page_%s' % page.name in perms
and use it:
{% if page|check_page_perms:perms %}

Iterate through model fields in jinja template

I have several models in my application, and as I will have some views doing the same thing (form + tables showing existing records in my model), but implemented dfferently because of the model, which will be different in each case, I was wondering if it was possible to make it generic.
I googled a bit and was not able to find anything relevant to my case.
What I would like to achieve:
In my view, I want to go through each object from the model that I passed to the template, for example:
return render_template('addstatus.html', form=form, statuses=Status.query.all(),
target_model="Status", fields=Status.__mapper__.c.keys())
But I want to have only one view, whatever the model will be, so I am not able to know in advance the fields of my model, the number of columns and so on.
I want to have something like that in my view:
{% for obj in objects %}
{% for field in obj.fields %} (not existing)
<h1> {{ field }} :: {{ obj.fields.field.value }}
{% endfor %}
{% endfor %}
Is it something possible? How can I achieve that?
You can add this method to your db.Model class (either by subclassing or by monkeypatching):
from sqlalchemy.orm import class_mapper, ColumnProperty
def columns(self):
"""Return the actual columns of a SQLAlchemy-mapped object"""
return [prop.key for prop in class_mapper(self.__class__).iterate_properties
if isinstance(prop, ColumnProperty)]
Then you can use {% for col in obj.columns() %} to iterate over the column names.
I had a similar issue, and wanted something purely Jinja2 based. This will return all of the keys/fields in your "obj" table, and worked well for me:
obj.__table__.columns._data.keys()
Here it is implemented in your sample code
{% for obj in objects %}
{% for field in obj.__table__.columns._data.keys() %}
<h1> {{ field }} :: {{ obj[field] }} </h1>
{% endfor %}
{% endfor %}

How to render individual radio button choices in Django?

If I have a model that contains a ChoiceField with a RadioSelect widget, how can I render the radio buttons separately in a template?
Let's say I'm building a web app that allows new employees at a company to choose what kind of computer they want on their desktop. This is the relevant model:
class ComputerOrder(forms.Form):
name = forms.CharField(max_length=50)
office_address = forms.Charfield(max_length=75)
pc_type = forms.ChoiceField(widget=RadioSelect(), choices=[(1, 'Mac'), (2, 'PC')])
On the template, how do I render just the Mac choice button? If I do this, it renders all the choices:
{{ form.pc_type }}
Somewhat naively I tried this, but it produced no output:
{{ form.pc_type.0 }}
(I found a few similar questions here on SO:
In a Django form, how do I render a radio button so that the choices are separated on the page?
Django Forms: How to iterate over a Choices of a field in Django form
But I didn't feel like they had good answers. Is there a way to resurrect old questions?)
Django 1.4+ allows you to iterate over the choices in a RadioSelect, along with the lines of
{% for choice in form.pc_type %}
{{ choice.choice_label }}
<span class="radio">{{ choice.tag }}</span>
{% endfor %}
I'm not sure if this change allows you to use the syntax you describe ({{ form.pc_type.0 }}) — if not, you could work around this limitation with the for loop above and a tag like {% if forloop.counter0 == 0 %}.
If you're tied to Django < 1.4, you can either override the render() method as suggested or go with the slightly-more-verbose-but-less-complicated option of building up the form field yourself in the template:
{% for choice in form.pc_type.field.choices %}
<input name='{{ form.pc_type.name }}'
id='{{ form.pc_type.auto_id }}_{{ forloop.counter0 }}' type='radio' value='{{ choice.0 }}'
{% if not form.is_bound %}{% ifequal form.pc_type.field.initial choice.0 %} checked='checked' {% endifequal %}
{% else %}{% ifequal form.pc_type.data choice.0 %} checked='checked' {% endifequal %}{% endif %}/>
<label for='{{ form.pc_type.auto_id }}_{{ forloop.counter0 }}'>{{ choice.1 }}</label>
{% endfor %}
(choice.0 and choice.1 are the first and second items in your choices two-tuple)
The rendering of the individual radio inputs is handled by the RadioSelect widget's render method. If you want a different rendering, subclass RadioSelect, change the render method accordingly, and then use your subclass as the field's widget.
I think the simply looking at what's available inside the for loop of a choice field will tell one what they need to know. For example, I needed the value to set a class surrounding the span of the option (for colors and such):
<div>
{% for radio_input in form.role %}
{# Skip the empty value #}
{% if radio_input.choice_value %}
<span class="user-level {{ radio_input.choice_value }}">{{ radio_input }}</span>
{% endif %}
{% endfor %}
</div>
There are several attributes as you can see that keep you from having to use the ordinal.
In Django 2.0+ you can subclass forms.RadioSelect and "simply" specify a template for rendering the radio fields:
class SlimRadioSelect(forms.RadioSelect):
template_name = 'includes/slim_radio.html'
where slim_radio.html contains a revised version of the combined template_name and option_template_name used by the default RadioSelect widget.
Note, the default RadioSelect widget template is low-level rendering and consists of heavily layered templates: include, conditional and loop logic tags abound.
You'll know you've arrived when you're digging around in packages/django/forms/templates/django/forms/widgets/input.html to get what you need.
One other oddity for overriding the default widget's template is that you must invoke the TemplatesSetting renderer or your subclass won't be able to find slim_radio.html in your project's normally accessible template paths.
To override RadioSelect's local-only template path lookup:
Add 'django.forms' to your INSTALLED_APPS;
Add FORM_RENDERER = 'django.forms.renderers.TemplatesSetting' to your settings.py.
This all seems harder than it should be, but that's frameworks. Good luck.

Method Output in Admin Detail-View

Is there an easy way of displaying method output in Djangos admin detail view? For the list view we have the list_display field, however i cannot find anything similar for the detail view. Of course, editing the template would be a way but isnt there something easier?
Creating a new template isn't that hard. I typically put info in a right column like this:
{% extends "admin/change_form.html" %}
{% block coltype %}colMS{% endblock %}
{% block content %}{{block.super}}
<div id="content-related">
{{original.method}}
</div>
{% endblock %}