has_delete_permission gets parent instance in django admin inline - django

I have a Booking model with a User foreign key. In the admin the bookings are inlined inside the user change page.
I want to prevent some bookings from being deleted (from the inline) when there are less then 24 hours before the booking AND the logged user is not in SuperStaff group.
So I define the BookingInline something like that:
class BookingInline(admin.TabularInline):
model = Booking
extra = 0
fk_name = 'bookedFor'
def has_delete_permission(self, request, obj=None):
if not request.user.profile.isSuperStaff() and obj.is24hoursFromNow():
return True
return False
This code is reached, but I get a User instance, instead of a Booking one (and an error, of course), thus cannot decide for each inlined booking if it could be deleted or not.
Isn't the has_delete_permission() method supposed to get the inlined object instance in this case? There is nothing about in the django docs...
I know the code is reached since I checked it using only the condition on user, and it actually hides the delete box for appropriate users.
I also tried to do it other way, through the Formset and clean() method, but it doesn't have the request parameter, so I get the desired instance, but not the user logged in.
I've searched for a solution for a few hours, but seems like the only way is to put a link from the inline to the full change page of a Booking object, and check the permissions when a user will attempt to regularly delete a Booking.
Any ideas how can that be done in an elegant way would be appreciated.

I was facing exactly the same problem today, and I think I've found an acceptable way to solve it. Here's what I did:
I had to make inlines deletable only if a particular field had a certain value. Specifically, as I'm dealing with generic tasks and assignments, only non-accepted tasks have to be deletable. In model terms:
class Task(models.Model):
STATUS_CHOICES = (
('PND', 'Pending'),
('ACC', 'Accepted'),
)
status = models.CharField( ----> If this != 'PND', inline instance
max_length=3, should not be deletable
choices=STATUS_CHOICES,
default=STATUS_CHOICES[0][0])
Since I couldn't use has_delete_permission within my admin.TabularInline class either, as it refers to the whole fieldset (i.e. all the inlines) and not to the single row, I went through the path of template overriding:
tabular.html:44-62 (original)
[...]
{% for fieldset in inline_admin_form %}
{% for line in fieldset %}
{% for field in line %}
{% if not field.field.is_hidden %}
<td{% if field.field.name %} class="field-{{ field.field.name }}"{% endif %}>
{% if field.is_readonly %}
<p>{{ field.contents }}</p>
{% else %}
{{ field.field.errors.as_ul }}
{{ field.field }}
{% endif %}
</td>
{% endif %}
{% endfor %}
{% endfor %}
{% endfor %}
{% if inline_admin_formset.formset.can_delete %}
<td class="delete">{% if inline_admin_form.original %}{{ inline_admin_form.deletion_field.field }}{% endif %}</td>
{% endif %}
[...]
tabular.html (overridden)
[...]
{% for fieldset in inline_admin_form %}
{% for line in fieldset %}
{% for field in line %}
{% if not field.field.is_hidden %}
<td{% if field.field.name %} class="field-{{ field.field.name }}"{% endif %}>
{% if field.is_readonly %}
<p>{{ field.contents }}</p>
{% else %}
{% include "admin/includes/field.html" with is_tabular=True %}
{% endif %}
</td>
{% endif %}
{% endfor %}
{% endfor %}
<!-- Custom deletion logic, only available for non-accepted objects -->
{% for line in fieldset %}
{% for field in line %}
{% if field.field.name == "status" %}
{% if field.field.value == "PND" %}
<td class="delete">{% if inline_admin_form.original %}{{ inline_admin_form.deletion_field.field }}{% endif %}</td>
{% else %}
<td class="delete"><input type="checkbox" disabled="disabled">
<img src="/static/admin/img/icon_alert.gif" data-toggle="tooltip" class="title-starter"
data-original-title="Can't remove accepted tasks" />
</td>
{% endif %}
{% endif %}
{% endfor %}
{% endfor %}
{% endfor %}
<!-- Classic deletion, removed
{% if inline_admin_formset.formset.can_delete %}
<td class="delete">{% if inline_admin_form.original %}{{ inline_admin_form.deletion_field.field }}{% endif %}</td>
{% endif %}
-->
[...]
As a result (non-standard graphic as I'm using django-admin-bootstrap):
Strictly talking about "elegance", I have to get through the lines' fields twice to make it work, but I haven't found any better way like directly reading that field's value. I couldn't have anything like {{ line.fields.0.status }} or {{ line.fields.status }} work. If anyone could point to the direct syntax, I'd gladly update my solution.
Anyway, since it still works and it's not really that bad, I'll be fine with this method until anything clearly better comes out.

You can check conditions in formset's clean() method.
class BookingFormSet(forms.BaseInlineFormSet):
def clean(self):
super().clean()
has_errors = False
for form in self.deleted_forms:
if form.instance.is24hoursFromNow():
form._errors[NON_FIELD_ERRORS] = self.error_class(['Not allowed to delete'])
has_errors = True
if has_errors:
raise forms.ValidationError('Please correct the errors below')
class BookingInline(admin.TabularInline):
model = Booking
formset = BookingFormSet
Note that you don't have request object here, so can't check for isSuperStaff()

Related

ListField is showing <ul> instead of <input> in edit/create post

I am using Flask, mongoengine for a project and I am trying to get basic stuff working from http://docs.mongodb.org/manual/tutorial/write-a-tumblelog-application-with-flask-mongoengine/
After implementing everything from above link I added a new field for "tags" in Post and when I try to create a post, my tags doesn't show a input box.
Any help is appreciated.
My code and screenshot below
class Post(db.DynamicDocument):
created_at = db.DateTimeField(default=datetime.datetime.now, required=True)
title = db.StringField(max_length=255, required=True)
slug = db.StringField(max_length=255, required=True)
comments = db.ListField(db.EmbeddedDocumentField('Comment'))
tags = db.ListField(db.StringField(max_length=30)) # New field I added
template form
{% macro render(form) -%}
<fieldset>
{% for field in form %}
{% if field.type in ['CSRFTokenField', 'HiddenField'] %}
{{ field() }}
{% else %}
<div class="clearfix {% if field.errors %}error{% endif %}">
{{ field.label }}
<div class="input">
{% if field.name == "body" %}
{{ field(rows=10, cols=40) }}
{% else %}
{{ field() }}
{% endif %}
{% if field.errors or field.help_text %}
<span class="help-inline">
{% if field.errors %}
{{ field.errors|join(' ') }}
{% else %}
{{ field.help_text }}
{% endif %}
</span>
{% endif %}
</div>
</div>
{% endif %}
{% endfor %}
</fieldset>
{% endmacro %}
rendering form code
{% extends "admin/base.html" %}
{% import "_forms.html" as forms %}
{% block content %}
<h2>
{% if create %}
Add new Post
{% else %}
Edit Post
{% endif %}
</h2>
<form action="?{{ request.query_string }}" method="post">
{{ forms.render(form) }}
<div class="actions">
<input type="submit" class="btn primary" value="save">
Cancel
</div>
</form>
{% endblock %}
From what I can gather, your problem is you're telling WTF to render the tags field, but WTForms doesn't know how to handle that information.
From looking at the Flask-MongoEngine documentation, it seems the ListField is just a FieldList as WTForms refers to it.
Currently you're not actually defining the form independently in WTForms, you're just using the magic included in Flask-MongoEngine, so my first attempt would be to add some more logic to your macro, add a {% elif field.type == 'ListField' %} and try and discover what's contained in there to iterate through to produce your form. From having a quick look at the source-code, something like the following might work.
{% elif field.type == 'ListField %}
{# render_the_group_label #}
{% for subfield in field.entries %}
{% if subfield.type == 'StringField' %}
{# render_the_subfield #}
{% endif %}
{% endfor %}
...
That code will need to be worked on, but hopefully it'll point you in the right direction. Otherwise, I'd actually define the form seperately in WTForms to give you a bit more control on the code-side. Luckily they provide a csv tag example which should help you if you need to go that route. I wrote a guide that takes a different route using #property decorators to achieve a similar effect, which again, might at least point you towards the finish line.

Django template: check for empty query set

Is there a way to check for an empty query set in the Django template? In the example below, I only want the NOTES header to be displayed if there are notes.
If I put an {% empty %} inside the "for" then it does display whatever is inside the empty tag, so it knows it's empty.
I'm hoping for something that does not involve running the query twice.
{% if notes - want something here that works %}
NOTES:
{% for note in notes %}
{{note.text}}
{% endfor %}
{% endif %}
Clarification: the above example "if notes" does not work - it still displays the header even with an empty query set.
Here's a simplified version of the view
sql = "select * from app_notes, app_trips where"
notes = trip_notes.objects.raw(sql,(user_id,))
return render_to_response(template, {"notes":notes},context_instance=RequestContext(request))
Edit: the view select selects from multiple tables.
Have a look at the {% empty %} tag.
Example from the documentation
<ul>
{% for athlete in athlete_list %}
<li>{{ athlete.name }}</li>
{% empty %}
<li>Sorry, no athletes in this list.</li>
{% endfor %}
</ul>
Link: https://docs.djangoproject.com/en/1.8/ref/templates/builtins/#for-empty
If you are interested in a table, or some kind of heading if there are results, add the forloop.first:
{% for athlete in athlete_list %}
{% if forloop.first %}
Athlete Name:
{% endif %}
{{ athlete.name }}
{% empty %}
Sorry, no athletes in this list.
{% endfor %}
Try {% if notes.all %}. It works for me.
In your view check whether notes is empty or not. If it is then you pass None instead:
{"notes": None}
In your template you use {% if notes %} as normal.
It's unfortunate that you're stuck using a raw query set - they're missing a lot of useful behavior.
You could convert the raw query set into a list in the view:
notes_as_list = list(notes)
return render_to_response(template, {"notes":notes_as_list},context_instance=RequestContext(request))
Then check it as a boolean in the template:
{% if notes %}
Header
{% for note in notes %}
{{ note.text }}
{% endfor %}
{% endif %}
You could also make it happen without conversions using forloop.first:
{% for note in notes %}
{% if forloop.first %}
Header
{% endif %}
{{ note.text }}
{% endfor %}
What about:
{% if notes != None %}
{% if notes %}
NOTES:
{% for note in notes %}
{{ note.text }}
{% endfor %}
{% endif %}
{% else %}
NO NOTES AT ALL
{% endif %}
Your original solution
{% if notes %}
Header
{% for note in notes %}
{{ note.text }}
{% endfor %}
{% endif %}
Works now with Django 1.7 and thanks to QuerySet caching, it does not cost and extra query.
Often the right way to do this is to use the {% with ... %} tag. This caches the query so it runs only once and also gives you more flexibility with your markup than using {% empty %}.
{% with notes as my_notes %}
{% if my_notes %}
<ul>
{% for note in my_notes %}
<li>{{ note }}</li>
{% endfor %}
</ul>
{% else %}
<p>Sorry, no notes available</p>
{% endif %}
{% endwith %}
With this particular example I'm not sure how useful it is but if you're querying Many-to-Many field, for instance, it's likely what you want to do.
Use {% empty %} in django templates
{% if list_data %}
{% for data in list_data %}
{{ data.field_1 }}
{% endfor %}
{% else %}
<p>No data found!</p>
{% endif %}
We can write above code with {% empty %}.
{% for data in list_data %}
{{ data.field_1 }}
{% empty %}
<p>No data found!</p>
{% endfor %}

Django admin add custom button in change form depending on a form field value

I have an application that overrides het Django change form. What I want to change is the submit buttons on the bottom. Underneath is change_form.html in a django app:
{% extends "admin/change_form.html" %}
{% block submit_buttons_bottom %}
## add some buttons
{% endblock %}
The button I want to show/add depends on the value of a certain field in the form names 'status'. How can I get the value of a field in the template... something like:
{% if form.field.status == 'unresolved' %}
<input type="submit" value="Mark as resolved" class="default" name="_save" />
{% endif %}
UPDATE:
I'm not getting any errors. There is simply nothing displayed.
Looping through var 'adminform' will get me to the field I need
{% for fieldset in adminform %}
{% for line in fieldset %}
{% for field in line %}
{% if field.field.name == 'status' %}
this is status {{ field.field.name }} - {{ field.contents }}
{% endif %}
{% endfor %}
{% endfor %}
{% endfor %}
But I want to access it directly. Something like:
{% if adminform.0.0.field.status == 'unresolved' %}
<input type="submit" value="Mark as resolved" class="default" name="_save" />
{% endif %}
This should work:
{% if adminform.form.status.value == 'unresolved' %}
or if the field is readonly, there's another way:
{% if adminform.form.instance.status == 'unresolved' %}
Try changing your if statement to -
{% if adminform.status.value == 'unresolved' %}
I'm guessing, but I think the adminform variable is probably just a form. Have a look at this section of the docs to see the atributes of the form fields.

Using custom object manager on related set

Im trying to print out 4 entries. It works, as long I don't have any entries not published.
How can I get a queryset that only contains objects from my "published" manager?
Now I use: {% if benefit.status == "p" %} to not print those entries not published, but then the unpublished effects the slice count.
#views.py:
class PackageListFrontpage(ListView):
context_object_name = "package_frontpage_list"
template_name = "frontpage.html"
queryset = Package.published.all().order_by('order')[:5]
#frontpage.html
{% for package in package_frontpage_list %}
<div>
<h3>{{ package.name }} >></h3>
<ul>
{% for benefit in package.benefit_set.all|slice:":4" %}
{% if benefit.status == "p" %}
<li>{{ benefit.name }}</li>
{% endif %}
{% empty %}
<li>There are no published benefits in this package</li>
{% endfor %}
</ul>
</div>
{% endfor %}
I guess there is a better way of doing this?
You could define a method on your Package model that returns the queryset of related benefits which are published.
class Package(object):
...
def benefit_set_published(self):
"""
Return the related benefits which are published
"""
return self.benefit_set.filter(status="p")
Then change your template to:
{% for benefit in package.benefit_set_published.all|slice:":4" %}
<li>{{ benefit.name }}</li>
{% empty %}
<li>There are no published benefits in this package</li>
{% endfor %}

Django fieldset.html customization. How to customize a single field?

In my app, I need to add javascript to the admin template "fieldset.html" in order to create a widget for a single field.
But, in the fields iteration (for), when I try to catch the specific field, I fail.
I'm doing by the ifequal tag (fieldset.html):
{% for line in fieldset %}
<div class="form-row{% if line.errors %} errors{% endif %}{% for field in line %} {{ field.field.name }}{% endfor %}">
{% for field in line %}
(...)
{% ifequal field.label_tag "name" %}
#do something
{% endifequal%}
(...)
{% endfor %}
</div>
{% endfor %}
Any sugestions? The field stores the recurrence of a schedule. So I need to do something dinamic, that's why I'm thinking about using javascript.
"Form Media"