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)
Related
First section of code works fine; it is for reference.
#Basic Model
class MyTestModel(models.Model):
record = models.CharField(max_length=100)
def __str__(self):
return self.record
#Specify verbose_name
class Meta:
verbose_name = 'UniqueNameExample'
verbose_name_plural = verbose_name
#Generic ListView.
class MyTemplateView(ListView):
model = MyTestModel
template_name = 'base.html'
context_object_name = 'model_list'
ordering = ['record']
#Python block in HTML template. So far, so good.
{% for item in model_list %}
{{ item.record }}<br>
#{{ item }} also works
{% endfor %}
I am trying to access the Model's verbose_name ('UniqueNameExample') AND the model_list in the view. I've tried registering a filter, a tag, and simple_tag.
Something like: templatetags/verbose.py
from django import template
register = template.Library()
#register.filter (or #register.tag or #register.simple_tag)
def verbose_name(obj):
#Could be verbose_name(model) or whatever input
return obj._meta.verbose_name
And then after
{% load verbose %}
in my HTML (which also works fine), I'll try something like this:
{{ object|verbose_name }}
And I'll get the error 'str' object has no attribute '_meta'. Error is the same if using a tag:
{% verbose_name object %}
Note: tags apparently worked for earlier versions, but maybe I'm using them incorrectly? Not asking to access the Model field verbose_name for "record," btw -- that's answered adequately on SO.
The one thing I've tried that gets the answer half right is if I set the following under MyTemplateView:
queryset = model._meta.verbose_name
The problem with this is it overrides the model_list, and the only result I'm left with is 'UniqueNameExample' without being able to access the record(s) I've used in the model.
I know private=True for _meta (not sure if that's relevant or worth exploring/possibly breaking), but Django admin displays the verbose_name (if set) in the list of created models, so I don't see why I can't do the same (also had a rough time tracing back exactly how it does it in the source code). Maybe it's not a generic ListView but a MixIn? Function-based?
Large(ish) db with thousands of models, each with unique verbose_name[s]; would very much like to keep it simple.
EDIT: Found a fantastic solution from Dominique Barton # https://blog.confirm.ch/accessing-models-verbose-names-django-templates/
First, create a templatags folder at the app level and populate with an init file. Next, create a template tag file. Something like verbose.py.
from django import template
register = template.Library()
#register.simple_tag
def verbose_name(value):
#Django template filter which returns the verbose name of a model.
#Note: I set my verbose_name the same as the plural, so I only need one tag.
if hasattr(value, 'model'):
value = value.model
return value._meta.verbose_name
Next, the ListView should be modified.
from django.views.generic.list import ListView as DjangoListView
from .models import MyTestModel
class ListView(DjangoListView):
#Enhanced ListView which includes the `model` in the context data,
#so that the template has access to its model class.
#Set normally
model = MyTestModel
template_name = 'base.html'
context_object_name = 'model_list'
ordering = ['record']
def get_context_data(self):
#Adds the model to the context data.
context = super(ListView, self).get_context_data()
context['model'] = self.model
return context
Don't forget to add the path to urls.py:
path('your_extension/', views.ListView.as_view(), name='base')
Lastly, load the tag and iterate through the "records" normally:
{% load verbose %}
<h1> {% verbose_name model%} </h1>
<ul style='list-style:none'>
{% for item in model_list %}
<li>{{ item }}}</a></li>
{% endfor %}
</ul>
Pagination also works as advertised.
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.
I am getting this error while I was trying to make two forms (with two models) and process that in the view. My SongForm is not saving its data in database while AlbumForm is perfectly saving its data.
views.py-
def formm(request):
if request.method=='POST':
songform = SongForm(request.POST)
albumform=AlbumForm(request.POST)
if songform.is_valid() and albumform.is_valid():
songform.save()
albumform=albumform.save(commit=False)
albumform.date=timezone.now()
albumform.save()
return redirect("result")
forms.py-
from django import forms
from . models import Album,Song
class SongForm(forms.ModelForm):
class Meta:
model=Song
fields=('song_title','genre')
class AlbumForm(forms.ModelForm):
class Meta:
model=Album
fields=('album_title',)
models.py-
from __future__ import unicode_literals
from django.db import models
class Album(models.Model):
album_title=models.CharField(max_length=50)
date=models.DateTimeField(blank=True,null=True)
def __str__(self):
return self.album_title
class Song(models.Model):
song_title=models.CharField(max_length=50)
genre=models.CharField(max_length=50)
album=models.ForeignKey(Album,on_delete=models.CASCADE)
def __str__(self):
return self.song_title
formmpage.html-
{% extends "musicapp/basepage.html" %}
{% block content %}
<form method="POST" class="post-form">
{% csrf_token %}
{{ songform.as_p }}
{{ albumform.as_p }}
<button type="submit" class="btn btn-info">POST</button>
</form>
{% endblock %}
Do correct me, where actually I am doing wrong. I guess it is in my views.py.
It looks as if you should be setting the song's album before you save it.
if songform.is_valid() and albumform.is_valid():
album = albumform.save(commit=False)
album.date = timezone.now()
album.save()
song = songform.save(commit=False)
song.album = album
song.save()
return redirect("result")
Note that I changed the code to album=albumform.save(commit=False), since the save() method returns a model instance, not a form instance.
Ok,I was certain this was the issue but i've used the code you've given me and I get an integrity error due to the fact that your Song model has a Foreign Key to the Album model without null=True it won't allow you to create a new Song without an Album model.
There has to be some kind of business logic to know whether a song belongs to an album. In my opinon you should have these 2 in separate endpoints.
When creating a Song you should be able to select from a list albums that it may or may not belong to based on a genre.
Anyways, you can get past this with just adding null=True on the the album Foreign key attribute in your Song model.
Suppose I have a model:
from django.db import models
class Test(models.Model):
name=models.CharField(max_length=255, verbose_name=u'custom name')
How do I get my model's field's verbose name in templates? The following doesn't work:
{{ test_instance.name.verbose_name }}
I would very much appreciate the solution, something on lines as we do when using forms, using label attribute in template:
{{ form_field.label }}
You can use following python code for this
Test._meta.get_field("name").verbose_name.title()
If you want to use this in template then it will be best to register template tag for this. Create a templatetags folder inside your app containing two files (__init__.py and verbose_names.py).Put following code in verbose_names.py:
from django import template
register = template.Library()
#register.simple_tag
def get_verbose_field_name(instance, field_name):
"""
Returns verbose_name for a field.
"""
return instance._meta.get_field(field_name).verbose_name.title()
Now you can use this template tag in your template after loading the library like this:
{% load verbose_names %}
{% get_verbose_field_name test_instance "name" %}
You can read about Custom template tags in official django documentation.
The method in the accepted answer is awesome!
And maybe you'll like this if you want to generate a field list.
Adding an iterable to the class Test makes it convenient to list fields' verbose name and value.
Model
class Test(models.Model):
...
def __iter__(self):
for field in self._meta.fields:
yield (field.verbose_name, field.value_to_string(self))
Template
{% for field, val in test_instance %}
<div>
<label>{{ field }}:</label>
<p>{{ val }}</p>
</div>
{% endfor %}
based on this answer https://stackoverflow.com/a/14498938 .in Django Model i added
class Meta:
app_name = 'myapp'
in listview i have
from django.core import serializers
context['data'] = serializers.serialize( "python", self.get_queryset() )
inside mylist.html i have
{% for field, value in data.0.fields.items %}
<th style="text-align:center;">{% get_verbose_field_name data.0.model field %}</th>
{% endfor %}
in filter:
from django import template
register = template.Library()
from .models import Mymodel
#register.simple_tag
def get_verbose_field_name(instance, field_name):
"""
Returns verbose_name for a field.
"""
myinstance = eval(instance.split('.')[1].title())
return myinstance._meta.get_field(field_name).verbose_name.title()
instance in the abbove filter for the specific example is myapp.mymodel i evalute instance into model object and the i return field verbose name
it works in django 1.9
It's probably too late for an answer but I had the same issue until I realised that I caused the problem by overriding the fields in the form.py (self.fields['fieldname'] = ..). If you do that you also need to set a label otherwise it uses a label derived from the fieldname.
Hope this quick reply makes sense.
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 %}