What is the best approach in a following situation?
Say we have some models, e.g. Article, Photo, BlogEntry and so on. Every model can be displayed on the page as a thumb or a so-called widget.
Example:
attribute thumbview of a model contains item thumb with title in html block
normalview - contains larger thumb, title and description in a block
bigview - thumb, title, description and say... number of comments addded
All of these should be in some way polymorphic in a template so I could do something like iterating through my list of abstract items (various types) and simply:
{{ item.thumbview }}
or
{{ item.bigview }}
to display every item thumb.
It can be achieved lazy-evaluated in a model but I don't feel hardcode'ing html in a model is the right way.
How can I model such a behaviour? What is the best way?
I would appreciate any suggestion. Thanks.
You can use a custom template tag and a standard method in a model to give a context to the widget in case you can't achieve some properties in a template:
myapp/models.py:
class Photo(models.Model):
...
def widget_context(self, context): # receives the context of the template.
user = context['request'].user # context should be RequestContext to contain request object (or you may use thread locals).
return {'tags': self.tag_set.filter(...), 'can_edit': self.owner == user or user.is_admin}
template tags file, widgets_app/templatetags/objwidgets.py:
#register.simple_tag(takes_context=True)
def object_widget(context, obj, size='small'):
context_func = getattr(obj, 'widget_context') # try getting the context method
extra_context = context_func(context) if context_func else {}
extra_context['obj'] = obj
long_tuple = (obj._meta.app_label, 'widgets', obj.__class__.__name__, size)
return render_to_string([ # multiple templates to have both generic and specific templates
'%s/%s/%s.%s.html' % long_tuple, # the most specific (photos/widgets/photo.small.html)
'%s/widget.%s.%s.html' % (obj._meta.app_label, obj.__class__.__name__, size),
'%s/widget.%s.html' % (obj._meta.app_label, size), # myapp/widget.small.html
'widget.%s.html' % size,
],
extra_context
context)
usage:
{% load objwidgets %}
{% object_widget photo1 'large' %}
{% object_widget photo2 %}
make a template for the object widget, myapp/widgets/photo.small.html:
<b>{{ obj.name }}</b>
<img src="{{ obj.thumbnail.url }}"/>
{% if can_edit %}edit{% endif %}
{% for t in tags %}
{{ tag.text }}
{% endif %}
You shouldn't generate html at all in your model. You can write some custom template tags to achieve what you need. If you are using django dev version, you can create an inclusion tag with an argument that could return a piece of html depending on the model type of the input.
Related
I am trying to pull data from two models within a DetailsView template (in Django). There is of course a primary model (eg. Articles) associated with the view, which is easy to access. However, I want to access data from a model (eg. Terms). I do not want to use ForeignKey because I will be using many 'Terms' in each 'Article,' and since ForeignKey will allow me to link to only row in the Terms model, I will have to set-up mutiple ForeignKey fields, which can get messy fast.
I was thinking that this could be accomplished using get_context_data or templatetags, but haven't have had any luck yet. Any thoughts?
From Django Documentation you can add any queryset or context value you like to call in your template context like book_list below will list all books and it doesn't has to be linked with any other models..
#views.py
class PublisherDetail(DetailView):
model = Publisher
def get_context_data(self, **kwargs):
# Call the base implementation first to get a context
context = super(PublisherDetail, self).get_context_data(**kwargs)
# Add in a QuerySet of all the books
context['book_list'] = Book.objects.all()
return context
#yourtemplate.html
{% for book in book_list %}
{% if book %}
{{ book.title }}
{% endif %}
{% empty %}
No book_list found.
{% endfor %}
I'm using Django to show a list of posts. Each post has a 'is_public' field, so if one post's 'is_public' equals to False, it should not be shown to the user. Also, I want to show a fixed number of posts in one page, but this number can be changing depending on views.
I decided to crop the queryset in template as a few views are using the same template, generating it in the view means a lot of repeated codes.
If written in python, it should look like this:
i=number_of_posts_to_show_in_one_page
while i:
if qs[i].is_public == True:
#show qs[i] to the page
i--
As the django template does not support while loop and for loop seems hard to control, is there a way of achieving this? Or should I do it in another way?(One idea is to crop the qs before looping)Thanks!
Update:
I've written this template tag to pre-process the queryset:
#register.simple_tag(takes_context=True)
def pre_process_list(context,list,numbers):
#if not user.has_perm('admin'):
context['result_list']=list.filter(is_public=True, is_removed=False)[0:numbers]
#else:
#context['result_list']=list[0:numbers]
return ''
Before using for loop in the template, I'll pass the queryset to this templage tag, and use a simple for loop to show its result.
If in the future I want to show non-public posts to admins(which is not decided yet), I can write in some logic like the commented ones, and have them styled differently in the template.
{% for post in posts %}
{% if post.is_public %}
{{ post }}
{% endif %}
{% endfor %}
Though this would be a perfect use case for a manager.
You could write a simple manager that filters public posts.
class PublicPostManager(models.Manager):
def get_query_set(self):
return super(PublicPostManager, self).get_query_set().filter(is_public=True)
Then you would add it to your Post Class:
class Post(models.Model):
...
public = PublicPostManager()
Then you could pass post.public.all() as public_posts to your template and simplify your loop:
{% for post in public_posts %}
{{ post }}
{% endfor %}
#arie has a good approach with the manager, but you can easily do the same without writing a manager:
# View
posts = Post.objects.filter(is_public=True) # or use the manager
# Now, you can either limit the number of posts you send
# posts = posts[:5] (only show five in the view)
return render_to_response('foo.html',{'posts':posts})
# Template
# Or you can do the limits in your template itself:
{% for post in posts|slice:":5" %}
{{ post }}
{% endfor %}
See the slice filter on more information.
However, since this is a common operation, with django 1.3 you can use class based views to automate most of this.
I'm implementing a custom permissions application in my Django project, and I'm lost as to how to implement a custom template tag that checks a logged in user's permissions for a specific object instance and shows a piece of HTML based on the outcome of the check.
What I have now is (pseudocode):
{% check_permission request.user "can_edit" on article %}
<form>...</form>
{% endcheck %}
('check_permission' is my custom template tag).
The templatetag takes in the user, the permission and the object instance and returns the enclosed HTML (the form). This currently works fine.
What I would like to do however, is something like:
{% if check_permission request.user "can_edit" on article %}
<form>...</form>
{% else %}
{{ article }}
{% endif %}
I've read about the assignment tag, but my fear is that I would pollute the context variable space with this (meaning I might overwrite previous permission context variables). In other words, as the context variables are being defined on different levels (the view, middleware in my case, and now this assignment template tag), I'm worried about maintainability.
You can use template filters inside if statements. So you could rewrite your tag as a filter:
{% if request.user|check_can_edit:article %}
Note that it's tricky to pass multiple arguments of different types to a filter, so you'll probably want to use one filter per permission, above I've used check_can_edit.
You can definitely do that if you're willing to write some more lines of python code to improve your template readability! :)
You need to parse the tag content yourself, even the parameters it takes and then resolve them, if you want to use variables on them.
The tag implemented below can be used like this:
{% load mytag %}
{% mytag True %}Hi{% else %}Hey{% endmytag %} Bro
Or with a variable:
{% mytag myobject.myflag %}Hi{% else %}Hey{% endmytag %} Bro
So, here's the way I did it:
from django.template import Library, Node, TemplateSyntaxError
register = Library()
#register.tag
def mytag(parser, token):
# Separating the tag name from the "test" parameter.
try:
tag, test = token.contents.split()
except (ValueError, TypeError):
raise TemplateSyntaxError(
"'%s' tag takes two parameters" % tag)
default_states = ['mytag', 'else']
end_tag = 'endmytag'
# Place to store the states and their values
states = {}
# Let's iterate over our context and find our tokens
while token.contents != end_tag:
current = token.contents
states[current.split()[0]] = parser.parse(default_states + [end_tag])
token = parser.next_token()
test_var = parser.compile_filter(test)
return MyNode(states, test_var)
class MyNode(Node):
def __init__(self, states, test_var):
self.states = states
self.test_var = test_var
def render(self, context):
# Resolving variables passed by the user
test_var = self.test_name.resolve(context, True)
# Rendering the right state. You can add a function call, use a
# library or whatever here to decide if the value is true or false.
is_true = bool(test_var)
return self.states[is_true and 'myvar' or 'else'].render(context)
And that's it. HTH.
In Django 2 the assignment tag was replaced by simple_tag() but you could store the custom tag result as a template variable:
# I'm assuming that check_permission receives user and article,
# checks if the user can edit the article and return True or False
{% check_permission user article as permission_cleared %}
{% if permission_cleared %}
<form>...</form>
{% else %}
{{ article }}
{% endif %}
Check the current doc about custom template tags: https://docs.djangoproject.com/en/2.1/howto/custom-template-tags/#simple-tags
inside my_tags.py
from django import template
register = template.Library()
#register.simple_tag(takes_context=True)
def make_my_variable_true(context):
context['my_variable'] = True
return '' # without this you'll get a "None" in your html
inside my_template.html
{% load my_tags %}
{% make_my_variable_true %}
{% if my_variable %}foo{% endif %}
In this case best solution is to use custom filter. If you don't want write long code for custom tag. Also if you don't want to copy/paste others code.
Here is an example
Inside templatetag
register = template.Library()
def exam_available(user, skill):
skill = get_object_or_404(Skill, id=skill)
return skill.exam_available(user)
register.filter('exam_available', exam_available)
Inside template
{{ request.user|exam:skill.id }}
or
{% if request.user|exam:skill.id %}
Since one of the main common of it is to use request.user or any specific object(id) inside model's custom method, so filtering that individual object or user is the easiest way to make it done. :)
I have a survey application that creates forms of dynamic size. I use the formula "question_id_part" where question is fixed, id is the id of the question being asked, and part is one of three parts.
In my template, I need to be able to group these by a category, so I loop through the categories in the survey, get all the questions in that category, then I have a template tag to get my form field.
{% load my_tags %}
...
{% for category in survey.category_set.all %}
<h3>{{category}}</h3>
{% for question in category.factor_set.all %}
{% get_field_for_question_part question.id form "type" %}
{% endfor %}
{% endfor %}
...
Then I have a corresponding template tag that looks like this:
#register.simple_tag
def get_field_for_question_part(question_id, form, part):
field_name = "question_%s_%s" % (question_id, part)
field = form.fields[field_name]
return BoundField(form, field, field_name)
My question is this:
By explicitly importing BoundField my template tag knows too much about how forms work internally and thus is brittle to future changes in the non-public behavior of forms. Thus, it seems to me that the BoundField should be accessible somehow as a method on field -- but for the life of me I can't figure out what that method would be. Am I missing something obvious?
Try:
#register.simple_tag
def get_field_for_question_part(question_id, form, part):
field_name = "question_%s_%s" % (question_id, part)
return form.__getitem__(field_name)
See method definition on line 101 here: django/forms/forms.py
Here are my models:
class Activity(models.Model):
title = models.CharField(blank=False, max_length=100)
description = models.TextField(blank=False)
class UserActivityWork(models.Model):
activity = models.ForeignKey(Activity)
user = models.ForeignKey(User)
hours_worked = models.FloatField()
comment = models.TextField()
Example data would be, an Activity of "climbing Mt Everest" and each user would be able to input how long it took them and a comment.
Here's my question: How can I display a list of all the Activities, and if the user has entered data for that Activity, display the pertinent details next to the Activity?
So far, I have considered:
creating a dictionary of
UserActivityWork with a key of the Activity id and a value of the user's UserActivityWork. This would be fine with
me, but I have no idea of how to do
this in django's templating system (ie, how do you say: {{ user_work[activity.id] }})
creating an object that would hold
both the Activity and
UserActivityWork. I haven't done this
one, because I am hoping that django
has a better way to do this.
Any insight would be greatly appreciated!
Assuming you have 2 querysets accessable from within your template (say as activities and user_activities)
A naive way would be to iterate over each activity and then over each user activity.
{% for activity in activities %}
{{ activity.title }}
{% for user_activity in user_activities %}
{% ifequal user_activity.activity activity %}
Display userdata
{% endifequal %}
{% endfor %}
{% endfor %}
Dictionary lookups can be performed in templates by using a dot (.)
Technically, when the template system encounters a dot, it tries the following lookups, in this order:
Dictionary lookup
Attribute lookup
Method call
List-index lookup
Another option would be to create a custom template tag. You could loop over the activity list as before and then pass the activity and either the user_activity list or the user to the tag to perform the lookup and render the required data.
Thanks for the hint, Gerry. I found that writing a custom template tag as you suggested was the way to go.
Here are the gory details, in case anyone stumbles across this.
In the view method, I published a dictionary "user_activity_status" which contains a key of activity.id and value of UserActivityWork object for the logged in user's work on that activity
This is the the relevant section of the template. Basically this going to add a variable "map_value" with a value of
getattr(user_activity_status[activity.id], "comment")
Here's the template:
{% load *file-name-of-the-templatetag-file* %}
{% access_map_method user_activity_status activity.id comment %}
{% if map_value %}
{{ map_value }}
{% else %}
get working sucka!
{% endif %}
here is the section of the templatetag file (see Gerry's links for the details of how to set this up)
from django import template
register = template.Library()
#register.tag(name="access_map_method")
def do_access_map_method(parser, token):
try:
tag_name, dict_name , key_name, method_name = token.contents.split()
except ValueError:
msg = '%r tag requires three arguments' % token.contents[0]
raise template.TemplateSyntaxError(msg)
return MapNode(dict_name , key_name, method_name)
class MapNode(template.Node):
def __init__(self, dict_name, key_name, method_name):
self.dict_var = template.Variable(dict_name)
self.key_var = template.Variable(key_name)
self.method_name = method_name
def render(self, context):
try:
dict_obj = self.dict_var.resolve(context)
key_obj = self.key_var.resolve(context)
if key_obj in dict_obj.keys():
if self.method_name:
context['map_value'] = getattr(dict_obj[key_obj], self.method_name)
else:
context['map_value'] = dict_obj[key_obj]
else:
context['map_value'] = ''
except template.VariableDoesNotExist:
context['map_value'] = ''
return ''