Using custom object manager on related set - django

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 %}

Related

How to loop all groups AND users within those groups in a Django template?

I am using the standard Django auth framework. How do I list all groups AND all users within those groups?
So far I am able to list all the known groups, but not the users within those groups.
For example in views.py I have:
def group_management(request):
group_list = Group.objects.all()
return render(request, "app/group_management.html", {"group_list": group_list})
In group_management.html I have:
{% if group_list %}
<ul>
{% for group in group_list %}
<li>{{ group.name }}</li>
{% endfor %}
</ul>
{% else %}
<p>No group are available.</p>
{% endif %}
How do I extend this loop to include users associated with in that group. E.g.
{% if group_list %}
<ul>
{% for group in group_list %}
<li>{{ group.name }}. USERS:
{% for user in group_user_list %}
{{user.username}} ,
{% endfor %}
</li>
{% endfor %}
</ul>
{% else %}
<p>No group are available.</p>
{% endif %}
I assume there is some extra code required in views.py but am unsure of where to start.
The User model is linked to the Group model via a ManyToMany relationship using the PermissionsMixin.
Thus, you don't need to add any extra code to your view. You can use a reverse lookup directly in the template, like so:
{% for group in group_list %}
<li>{{ group.name }}. USERS:
{% for user in group.user_set.all %}
{{user.username}} ,
{% endfor %}
</li>
{% endfor %}
You can read more on reverse lookups here.
You can do this:
group_list = Group.objects.all()
for group in group_list:
group.user_list = User.objects.filter(groups=group)
Then you can, for example, use it in this way:
for group in group_list:
print(group)
for user in group.user_list:
print(user)

How to pass a date (year) parameter to my url?

This is a huge rookie mistake but I can't figure it out.
This is what I wanna do:
I have a page displaying a list a years where an objects is available.
I want that, when I click on a year, it takes me to the corresponding YearArchiveView. I just don't succeed in passing the right parameter to the URL. Passing a template tag obviously doesnt work so what is the right way to do it ?
I get this error:
TemplateSyntaxError at /receipts/
Could not parse some characters: |{{y||date:"Y"}}
My template:
<ul>
{% for y in years_available %}
<li>{{y|date:"Y"}}</li>
{% empty %}
<p>No Receipt Yet</p>
{% endfor %}
</ul>
My view:
class ReceiptListView(LoginRequiredMixin, ListView):
model = Receipt
template_name = 'receipts.html'
def get_queryset(self):
queryset = Receipt.objects.dates('date_created','year',order="DESC")
return queryset
def get_context_data(self, *args, **kwargs):
context = super(ReceiptListView, self).get_context_data(**kwargs)
context['years_available'] = Receipt.objects.dates('date_created',
'year', order="DESC")
return context
My urls.py:
url(r'receipts/(?P<year>[0-9]{4}/$)',views.ReceiptYearArchiveView.as_view(),
name='receipt_year_archive'),
you dont need year=
just use this
<ul>
{% for y in years_available %}
{% with y|date:"Y" as current_year %}
<li>{{y|date:"Y"}}</li>
{% endwith %}
{% empty %}
<p>No Receipt Yet</p>
{% endfor %}
</ul>
You can't add another {{ and }} inside {%. It should call with direct variable.
<ul>
{% for y in years_available %}
<li>{{ y|date:"Y" }}</li>
{% empty %}
<p>No Receipt Yet</p>
{% endfor %}
</ul>
But, I think your case similiar with this docs examples:
<ul>
{% for yearvar in year_list %}
<li>{{ yearvar }} Archive</li>
{% endfor %}
</ul>
If the output of years_available is a list of integer years.
eg: [1992, 2001, 2005, 2011, 2014]
It should be:
<ul>
{% for y in years_available %}
<li>{{ y }}</li>
{% empty %}
<p>No Receipt Yet</p>
{% endfor %}
</ul>

RoutablePageMixin and breadcrumbs

A standard Wagtail breadcrumb system like this works perfectly if all of your pages are in a tree (parents/children):
{% block main %}
{% if self.get_ancestors|length > 1 %}
<ul class="breadcrumb">
{% for page in self.get_ancestors %}
{% if page.is_root == False and page.url != '/' %}
<li>{{ page.title }}</li>
{% endif %}
{% endfor %}
<li class="active">{{ self.title }}</li>
</ul>
{% endif %}
{% endblock main %}
But it falls down if some of your sub-pages are not actual children, but instead use RoutablePageMixin. Because the routable pages are really different instances of the parent, the breadcrumb trail stops short of making it down to the routable page.
I thought I could add some extra info to the context to detect the situation and special-case it, but all of the WT URL methods return the URL of the "parent" page (i.e. the actual instance), and besides there is no programmatic "title" that could be used in the breadcrumb.
What's the best way to have a breadcrumb system that works equally well for child pages and routable pages?
Answering my own question (Thanks Robert for the hint). In the route definition in the model, add something like:
ctx['routed_title'] = 'Staff'
Then modify the breadcrumb example above like this (check for existence of the new element on context and append to breadcrumbs):
{% block main %}
{% if self.get_ancestors|length > 1 %}
<ul class="breadcrumb">
{% for page in self.get_ancestors %}
{% if page.is_root == False and page.url != '/' %}
<li>{{ page.title }}</li>
{% endif %}
{% endfor %}
{# If this is a routable, add non-parent/child link from context #}
{% if routed_title %}
<li>{{ page.title }}</li>
<li class="active">{{ routed_title }}</li>
{% else %}
<li class="active">{{ self.title }}</li>
{% endif %}
</ul>
{% endif %}
{% endblock main %}
Maybe this can be of any help.
#route(_(r'^detail/(?P<activity_slug>[-\w]+)/$'))
def show_activity(self, request, activity_slug):
activity_model_class = self.activity_model_class
if not activity_model_class:
raise Http404('No activity model.')
else:
queryset = self.get_activity(activity_slug)
try:
activity = queryset.get()
except activity_model_class.DoesNotExist:
raise Http404('activity not found')
else:
self.current_url = self.get_url(
'show_activity',
kwargs = {'activity_slug': activity_slug}
)
Now the routable page has a current_url
def get_context(self, request, *args, **kwargs):
context = super().get_context(request)
context['current_url']= self.current_url
return context
And now it’s in the context.

has_delete_permission gets parent instance in django admin inline

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()

set_all to get value from custom model manager in django

I have two models:
Tutorial
--> consist of published manager which returns queryset when is_published=True
Category
In template, I am passing Category object.
{% for category in categories %}
{% for tutorial in category.tutorial_set.all %}
{{ tutorial.title }}
{% endfor %}
{% endfor %}
Instead of getting all, I want to get from published manager like: Tutorials.published.all()
How to achieve this?
Well I guess you can do something like
class TutuorialManager(models.Manager):
def published(self):
return self.filter(is_published = True)
then in the views you can do something like..
{% for category in categories %}
{% for tutorial in category.tutorial_set.published.all %}
{{ tutorial.title }}
{% endfor %}
{% endfor %}