Django tagging select_related - django

I using Django tagging project.
That is was very stable project.
Working on django 1.3.
But i have a problem.
# in models.py
from tagging.fields import TagField
class Blog(models.Model):
title = models.CharField(max_length = 300)
content = models.TextField()
tags = TagField()
author = models.ForeignKey(User)
# in views.py
def blog_list(request):
# I Want to use select related with tags
blogs = Blog.objects.all().select_related("user", "tags") # ????
....
# in Templates
{% load tagging_tags %}
{% for blog in blogs %}
{% tags_for_object blog as tags %}
{{blog.title}}
{% for tag in tags %}
{{tag}}
{% endfor %}
{% endfor %}

django-tagging uses a generic foreign key to your model, so you can't just use select_related.
Something like this should do the trick though:
from django.contrib.contenttypes.models import ContentType
from collections import defaultdict
from tagging.models import TaggedItem
def populate_tags_for_queryset(queryset):
ctype = ContentType.objects.get_for_model(queryset.model)
tagitems = TaggedItem.objects.filter(
content_type=ctype,
object_id__in=queryset.values_list('pk', flat=True),
)
tagitems = tagitems.select_related('tag')
tags_map = defaultdict(list)
for tagitem in tagitems:
tags_map[tagitem.object_id].append(tagitem.tag)
for obj in queryset:
obj.cached_tags = tags_map[obj.pk]

I have recently encountered the same problem and solved it without a single db query. Indeed, we don't need tag id to get url. As tag name is unique and is db_index you can get url using name field instead of id. E.g.
# your_app/urls.py
url(r'tag/(?P<tag_name>[-\w]+)$', tag_detail_view, name='tag_detail')
Also, tagging TagField gives us a string with tag names, like 'python,django'. So we can write a custom template filter:
# your_app/templatetags/custom_tags.py
from django.urls import reverse
#register.filter
def make_tag_links(tags_str):
return ', '.join([u'%s' % (reverse(
'tag_detail', args=[x]), x) for x in tags_str.split(',')])
And then you can write in template:
# your_list_template.html
{% for blog in blogs %}
{{blog.title}}
{% if blog.tags %}
{{ blog.tags|make_tag_links|safe }}
{% endif %}
{% endfor %}

Related

Is there a way to pass model attribute into another attribute?

I'm using {% autoescape off %} to render html that I add through admin page. I want to get another variable of the model.
post.html
{% autoescape off %}
{{ post.content }}
{% endautoescape %}
Is it possible to pass another attribute of the same model into post.content? Something like that
post.content
<img src="{{ post.main_image.url }}">
Yup. Assuming there is a post, and it has a main_image, which has a url, there shouldn't be any problem. You may want to check in the template if you are not sure yourself first though. So to be safer, you should do:
{% if post and post.main_image %}
<img src="{{ post.main_image.url }}">
{% endif %}
Ok, I've finally made it. My goal was to create CMS-ish admin page of a model, where I could add raw html, django tags and variables directly to the content attribute along with other attributes like titles, categories, images and so on. I've managed to do it by using and customizing django-dbtemplates package.
pip install django-dbtemplates
First, fork template model from dbtemplates and add your model as a foreign key
blog/models.py
from dbtemplates.models import Template as CoreTemplate
class Template(CoreTemplate):
post = models.ForeignKey(Post, on_delete=models.CASCADE)
Then just customize your admin.py to show template field as an attribute
blog/admin.py
from .models import Post, Template
class TemplateInline(admin.TabularInline):
model = Template
extra = 0
class PostAdmin(admin.ModelAdmin):
inlines = [TemplateInline, ]
Optional
You can generate html names of you template based on your model slug by modifying save function of the Template class
blog/models.py
from dbtemplates.models import Template as CoreTemplate
from dbtemplates.conf import settings
class Template(CoreTemplate):
post = models.ForeignKey(Post, on_delete=models.CASCADE)
def save(self, *args, **kwargs):
self.last_changed = now()
slug = self.post.slug
self.name = f'{slug}-content.html'
if settings.DBTEMPLATES_AUTO_POPULATE_CONTENT and not self.content:
self.populate()
super(Template, self).save(*args, **kwargs)
Then in your html you can render this template with context manager
post.html
{% with post.slug|add:'-content.html' as content %}
{% include content %}
{% endwith %}
Now in your admin settings you can have just one big content field from basic Template class
blog/admin.py
from .models import Template
from django.forms import Textarea
class TemplateInline(admin.TabularInline):
model = Template
extra = 0
fields = ('content',)
formfield_overrides = {
models.TextField: {'widget': Textarea(attrs={'rows': 40, 'cols': 150})},
}
To remove default dbtemplates Template class from your admin panel just unregister it in your admin settings
blog/admin.py
from dbtemplates.admin import Template as CoreTemplate
admin.site.unregister(CoreTemplate)

'My_Model' object is not iterable using customtag in django

I am new to Django and I am trying to make my data accessible to templates in different apps by creating custom tags in Django.
my model.py
from django.db import models
class my_Model(models.Model):
name = models.CharField(max_length=20)
age = models.CharField(max_length=20)
my custom tag file templatetag/custom_tag.py(why I did this is to make my data accessible to templates in different apps)
from django import template
from model_file.models import my_Model
register = template.Library()
#register.simple_tag
def get_custom_tag_fn():
return my_Model.objects.order_by('-pk')[0]
my html file
{% load custom_tag %}
{% get_custom_tag_fn as ct %}
{% for item in ct %}
<p> {{ item }} </p>
{% endfor %}
I am getting the error 'My_Model' object is not iterable. Any thought about how to solve this.
The problem is here:
return my_Model.objects.order_by('-pk')[0]
Model objects.order_by(...) returns queryset, alike to the list it`s a collection, so with [0] index you take the first object of that collection which of course is not iterable (it's just a single object). So, after that, when you trying to iterate:
{% for item in ct %}
...
you`re catching this error.

Django taggit - retrieving tags into a template

I've set up django-taggit and it's working fine, all tags are listed under tags in admin and I can add tags through the admin and in a form.
I'm having real trouble listing the tags in a template (basically I want a long list of all objects with title, url and tags.
Currently I have a method called return tags attached to the model which should return a list of tags for me to iterate over in the template. Well... that is theory...
Model.py
class DefaultResource(models.Model):
#
# This class is the parent class for all resources in the media manager
#
title = models.CharField(max_length=100)
created_date = models.DateTimeField(auto_now_add=True, auto_now=False)
edited_date = models.DateTimeField(auto_now_add=False,auto_now=True)
level = models.ManyToManyField(AssoeLevel)
agebracket= models.ManyToManyField(AgeBracket)
pathway= models.ManyToManyField(AssoePathway)
tags = TaggableManager()
slug = models.SlugField(max_length=100,editable=False,blank=True)
updownvotes = RatingField(can_change_vote=True)
views = models.DecimalField(max_digits=20,decimal_places=2,default=0,blank=True)
score = models.DecimalField(max_digits=20,decimal_places=4,default=0,blank=True)
icon = models.CharField(max_length=254,editable=False,blank=True)
def return_tags(self):
taglist = self.tags.names()
return taglist
view.py
def index(request):
context = RequestContext(request)
default_resource_list = DefaultResource.objects.order_by('-score')
context_dict = {'default_resource_list':default_resource_list}
return render_to_response('mediamanager/index.html', context_dict, context)
index.html
{% for resource in default_resource_list %}
{% for tag in resource.return_tags %}
{{ tag }}
{% endfor %}
{% endfor %}
Currently this is returning an empty list.
I've also tried putting the following into the template
{% for tag in resource.tags.all %}
{{tag.name}}
{% endfor %}
But this also returns an empty list
I am still trying to figure this out, as I want a list of all tags as links to posts that contain that tag. I've only been able to do this by using django-taggit AND django-taggit-templatetags.
Depending on the version of Django you are running try these:
For earlier versions (before 1.5)
Django-taggit-templatetags
For 1.5 or greater (I'm running 1.10 and this worked great)
Django-taggit-templatetags2
Next I am going to try Django tagging, as the docs are much more complete.
To Jon Clements, I am new to StackOverflow and not sure why my answer was deleted. If you have a better solution to this please advise.
This works on django-taggit 1.3.0:
{% for tag in posts.tags.all %}
{{ tag.name }}
{% endfor %}

Django prefetch related no duplicates with intermediate table

I have a question I am trying to solve for one day now.
With the models
class Quote(models.Model):
text = models.TextField()
source = models.ForeignKey(Source)
tags = models.ManyToManyField(Tag)
...
class Source(models.Model):
title = models.CharField(max_length=100)
...
class Tag(models.Model):
name = models.CharField(max_length=30,unique=True)
slug = models.SlugField(max_length=40,unique=True)
...
I am trying to model the world of quotes. with relationships: one Source having many Quotes, one Quote having many Tags.
Problem is:
How do I get all Tags that are contained in a Source (through the contained Quotes) ?
with the minimum possible queries.
with the amount of times they are contained in that source
I have tried the naive one with no prefetch related, with a model method
def source_tags(self):
tags = Tag.objects.filter(quote__source__id=self.id).distinct().annotate(usage_count=Count('quote'))
return sorted(tags, key=lambda tag:-tag.usage_count)
And in the template:
{% for tag in source.source_tags|slice:":5" %}
source.quote
{% endfor %}
Now I have
sources = Source.objects.all().prefetch_related('quote_set__tags')
And in the template I have no idea how to iterate correctly to get the Tags for one source, and how I would go about counting them instead of listing duplicate tags.
This will get the result in a single SQL query:
# views.py
from django.db.models import Count
from .models import Source
def get_tag_count():
"""
Returns the count of tags associated with each source
"""
sources = Source.objects.annotate(tag_count=Count('quote__tags')) \
.values('title', 'quote__tags__name', 'tag_count') \
.order_by('title')
# Groupe the results as
# {source: {tag: count}}
grouped = {}
for source in sources:
title = source['title']
tag = source['quote__tags__name']
count = source['tag_count']
if not title in grouped:
grouped[title] = {}
grouped[title][tag] = count
return grouped
# in template.html
{% for source, tags in sources.items %}
<h3>{{ source }}</h3>
{% for tag, count in tags.items %}
{% if tag %}
<p>{{ tag }} : {{ count }}</p>
{% endif %}
{% endfor %}
{% endfor %}
Complementary tests :)
# tests.py
from django.test import TestCase
from .models import Source, Tag, Quote
from .views import get_tag_count
class SourceTags(TestCase):
def setUp(self):
abc = Source.objects.create(title='ABC')
xyz = Source.objects.create(title='XYZ')
inspire = Tag.objects.create(name='Inspire', slug='inspire')
lol = Tag.objects.create(name='lol', slug='lol')
q1 = Quote.objects.create(text='I am inspired foo', source=abc)
q2 = Quote.objects.create(text='I am inspired bar', source=abc)
q3 = Quote.objects.create(text='I am lol bar', source=abc)
q1.tags = [inspire]
q2.tags = [inspire]
q3.tags = [inspire, lol]
q1.save(), q2.save(), q3.save()
def test_count(self):
# Ensure that only 1 SQL query is done
with self.assertNumQueries(1):
sources = get_tag_count()
self.assertEqual(sources['ABC']['Inspire'], 3)
self.assertEqual(sources['ABC']['lol'], 1)
I have basically used the annotate and values functions from the ORM. They are very powerful because they automatically perform the joins. They are also very efficient because they hit the database only once, and return only those fields which are specified.

Add custom html to choicefield label in django

I am struggling with a requirement now, I want to add an image to choice field label and I really dont have a clue how to do it. I am using django form wizard to render the form.
Here is the image which shows what I want to achieve :
And here is what I have got right now ( to make the radio buttons inline, I know it could be achieved through css):
Here is the forms.py:
from django import forms
from django.utils.translation import gettext as _
CHOICES=[('0','Pay by card'), ('1','Invoice')]
class PaymentForm(forms.Form):
title = 'payment'
payment_method = forms.ChoiceField(label = _("Payment Options"), choices=CHOICES, widget=forms.RadioSelect(), required = True)
I am rendering using wizard form:
{{ wizard.form.payment_method.label_tag }}
{{ wizard.form.payment_method|safe }}
{{ wizard.form.payment.errors}}
Anyone has any suggestion for this apart from custom widget?
Without a widget:
from django.utils.safestring import mark_safe
CHOICES=[('0', mark_safe('Pay by card <img src="by_card.jpg"/>')),
('1', mark_safe('Invoice <img src="no_card.jpg"/>'))
]
Credit: setting help_text for each choice in a RadioSelect
1) Shortly (but i'm not sure) call a function where 'Pay by card' and return all <img>... that you need.
2) You can make somthing like #Gahbu said
3)Long [Better, i think, but untested :( ]:
Make a renderer:
from myapp.my_widgets import CardsRadioFieldRenderer
CARD_CHOICE = '0'
CHOICES=[(CARD_CHOICE,'Pay by card'), ('1','Invoice')]
class PaymentForm(forms.Form):
title = 'payment'
payment_method = forms.ChoiceField(label = _("Payment Options"), choices=CHOICES,widget=forms.RadioSelect(renderer=CardsRadioFieldRenderer), required = True)
# myapp/my_widgets.py
class CardRadioInput(RadioInput):
def __init__(self, name, value, attrs, choice, index):
self.name, self.value = name, value
self.attrs = attrs
choice_value = force_text(choice[0])
self.choice_value = choice_value
if choice_value == CARD_CHOICE:
choice_label = force_text(self.get_html_for_card_choice())
else:
choice_label = force_text(choice[1])
self.choice_label = choice_label
self.index = index
def get_html_for_card_choice(self):
#some logic to get the images tags (<img ...> <img ...>)
return text
class CardsRadioFieldRenderer(RadioFieldRenderer):
def __getitem__(self, idx):
choice = self.choices[idx] # Let the IndexError propogate
return CardRadioInput(self.name, self.value, self.attrs.copy(), choice, idx)
In your template do something like :
{% for choice in wizard.form.payment_method.choices %}
{{ choice.0 }} {# value #} {{ choice.1 }} {# value #}
{% if choice.0 == PAYMENT_BY_PAYPAL %}
...
{% endif %}
{% endfor %}
You can also write :
{% for key, value in wizard.form.payment_method.choices %}
{% if key == PAYMENT_BY_PAYPAL %}
...
{% endif %}
{% endfor %}
If you want to tweak it from the template, you need to use the "safe" which tells Jinja not to escape it. like below
{{ some_object.some_property|safe }}
Yet if you want to use it inside your python code while defining the form/modelform just use mark_safe as below.
mark_up = 'this is a link'
choices = list()
choices.append(('some_id', mark_safe(mark_up)))
self.fields['some_field'].choices = choices