Django basics: how to render a context with a class based view - django

My 'Note' model has a Charfield called 'tags'. I want to take the Note.tags string and render it as a . I have a method that will give me a python list and I am sort of hoping that I can use the form method '.as_ul' in the template. But I can't seem to get the variable into the template. Here is what I am trying:
My view class:
import string
...
class NoteDetailView(generic.DetailView):
model = Note
template_name = 'note_taker/note'
def tag_string_to_list(self):
tag_string = Note.tags
tag_list = string.split(tag_string)
return render(template_name, Context({'tag_list':tag_list}, note_taker))
My template:
<ul>
{{ tag_list.as_ul }}
</ul>
even if I am wrong about how to use '.as_ul' I can't even render the list with {{ tag_list }}
I suppose I am not understanding how view methods work then.

Use the get_context_data method.
class NoteDetailView(generic.DetailView):
def get_context_data(self, **kwargs):
context = super(NoteDetailView, self).get_context_data(**kwargs)
context['tag_list'] = Note.tags.split()
return context
Within the template, you won't be able to use .as_ul, but there is a built in filter unordered_list that will probably do what you want:
<ul>
{{ tag_list|unordered_list }}
</ul>
Although you should really consider defining a standalone Tag model and using a many-to-many relationship rather than just a char field. This is one of the classic examples of many-to-many relationships. Or using one of the third-party Django tagging packages.

I always use Django's standard ContextMixin. It makes sure that the view object is available in the template as view.
So the view becomes like
class NoteDetailView(generic.ContextMixin, generic.DetailView):
model = Note
template_name = 'note_taker/note'
def tag_string_as_list(self):
return Note.tags.split()
And in the view you do:
<ul>{{ view.tag_string_as_list }}</ul>

Related

Pass other object/data into Flask Admin model view edit template

I'm extending the edit template for a ModelView so that I can show some other information from the database that is relevant for determining how to edit the record in this view. I know how to extend the template and get it to work, but I can't figure out how to query an object and use it in the template.
Also I need to use the value from the model/record in querying the new object I need to pass.
Here is my code from init.py:
class MilestoneView(ModelView):
edit_template = '/admin/milestone_model/milestone_edit.html'
can_delete = True
#i need something like this to work:
referrals = Referral.query.filter_by(email=model.email)
#then i need to pass referrals into the template
admin = Admin(app, name="My App", template_mode='bootstrap3')
admin.add_view(MilestoneView(Milestone, db.session, name='Milestones'))
Then from milestone_edit.html, I want something like this to work:
{% extends 'admin/model/edit.html' %}
{% block body %}
{{ super() }}
{% for r in referrals %}
<p>{{ r.name }}</p>
{% endif %}
{% endblock %}
But of course the referrals object is not available to use in the template. How do I customize this ModelView in order to pass this object in from the init file? I've reviewed the available posts on this subject(ish) on here and haven't found an answer. Thanks in advance.
Override your view's render method, see code on Github, and test if the view being rendered is the edit view. Now you can inject any data into the kwargs parameter. For example:
class MilestoneView(ModelView):
def render(self, template, **kwargs):
# we are only interested in the edit page
if template == 'admin/model/milestone_edit.html':
# Get the model, this is just the first few lines of edit_view method
return_url = get_redirect_target() or self.get_url('.index_view')
if not self.can_edit:
return redirect(return_url)
id = get_mdict_item_or_list(request.args, 'id')
if id is None:
return redirect(return_url)
model = self.get_one(id)
if model is None:
flash(gettext('Record does not exist.'), 'error')
return redirect(return_url)
referrals = Referral.query.filter_by(email=model.email)
kwargs['referrals'] = referrals
return super(MilestoneView, self).render(template, **kwargs)
Note how the model is retrieved. This is a direct copy of the code in method edit_view code. Adjust the code for your use-case.
Use the variable referrals in your edit Jinja2 template.
The render method is called in the following routes for each view:
'/' - i.e. the list view code
'/new/' - code
'/edit/' - code
'/details/' - code

How to Pull info from another model within a DetailsView template (without ForeignKey)?

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

Django 1.11 CreateView adding datepicker for datefields

So I am trying to change my form's model Datefield output to the Datepicker similar to DatepickerWidget in CreateView
The forms are generated using a html template:
{% for field in form %}
<div class="form-group">
<div class="col-sm-offset-2 col-sm-10">
<span class="text-danger small">{{ field.error }}</span>
</div>
<label class="control-label col-sm-2">{{field.label_tag}}</label>
<div class="col-sm-10">{{field}}</div>
</div>
{% endfor %}
Here is the Views with what I tried:
class newenv_form(generic.CreateView):
model = Environment
fields =['name', 'description', 'creation_date', 'status','status_update_date']
template_name = 'catalogue/new_env.html'
#Does not work
def get_form(self, form):
form = super(newenv_form, self)
form.fields['creation_date','status_update_date'].widget = forms.DateInput(attrs={'class':'datepicker'})
return form
Here is what worked but it is a dropdown datepicker that is limited in choices
def get_form(self):
'''add date picker in forms'''
from django.forms.extras.widgets import SelectDateWidget
form = super(EnvironmentCreateView, self).get_form()
form.fields['creation_date'].widget = SelectDateWidget()
return form
Note that I remove form_class which was causing problems
UPDATE: On Django 3.1, you can find SelectDateWidget within django.forms.widgets
Try to change the following line in the method get_form:
form = super(newenv_form, self)
to:
form = super(newenv_form, self).get_form(form)
And please follow the conventions and use PascalCase for class names in python.
You could call this class EnvironmentCreateView. Further generic view classes could be called for example EnvironmentListView, EnvironmentDetailView, EnvironmentUpdateView, EnvironmentDeleteView.
Using the same pattern for all your model classes will produce comprehensible code.
EDIT (2017-10-24):
Regarding your comment here is a further explanation. Although it is hard to give a correct remote diagnosis, I'd suggest the following changes:
class EnvironmentCreateView(generic.CreateView):
# class attributes ...
def get_form(self, form_class=None):
form = super(EnvironmentCreateView, self).get_form(form_class)
# further code ...
The essential changes are in bold. The class name is changed to meet the conventions. Also the parameter form is changed to form_class to meet the convetions, too. I emphasise conventions in particular, because it makes the code very comprehensible to other people familiar with the framework.
The important change is that form_class has the initial value None.
That should solve the problem with the error.
In the body of the method you call the parent method with super and write after that your custom code.
Please check the documentation for generic.CreateView. It inherits, among others, from generic.FormMixin. That is the class with the method get_form.

How can I tell if I'm doing a create or an update with django generic views (CreateView vs UpdateView) from my template?

I'm sharing the same template for my CreateView and UpdateView using django's generic views. I want the "submit" button in my template to say "Add" when I'm using the CreateView and "Update" when I'm using the UpdateView. Is there any way in my template to distinguish which view is being used (CreateView vs UpdateView)?
I know I could use a separate template using template_name_suffix and put the common stuff in a separate include or something but just wanted to see if there was a way to do it without creating a separate template.
When creating a new object, object will always be None, at the moment the template is rendered. You could check for the existence of {{ object }} in your template:
{% if object %}Update{% else %}Add{% endif %}
Override get_context_data and add a flag in your view:
def get_context_data(self, **kwargs):
context = super(YourClass, self).get_context_data(**context)
context['create_view'] = True
return context
Change YourClass to your Class View name
Then in your template you can:
{% if create_view %}

Django and fieldsets on ModelForm

I know you can specify fieldsets in django for Admin helpers. However, I cannot find anything useful for ModelForms. Just some patches which I cannot use. Am I missing something? Is there a way I could achieve something like fieldsets without manually writing out each field on my template in the appropriate tag.
I would ideally like to iterate through a set of BoundFields. However, doing something like this at the end of my ModelForm:
fieldsets = []
fieldsets.append(('Personal Information',
[username,password,password2,first_name,last_name,email]),) # add a 2 element tuple of string and list of fields
fieldsets.append(('Terms & Conditions',
[acceptterms,acceptprivacy]),) # add a 2 element tuple of string and list of fields
fails as the items contained in my data structure are the raw fields, not the BoundFields. t looks like BoundFields are generated on the fly... this makes me sad. Could I create my own subclass of forms.Form which contains a concept of fieldsets (even a rough one that is not backward compatible... this is just for my own project) and if so, can you give any pointer? I do not want to mess with the django code.
I think this snippet does exactly what you want. It gives you a Form subclass that allows you to declaratively subdivide your form into fieldsets and iterate through them in your template.
Update: that snippet has since become part of django-form-utils
Fieldsets in modelforms are still in "design" stage. There's a ticket in Django trac with low activity.
It's something I've been interested in researching myself in the near future, but since I haven't done it yet the best I can offer are these snippets:
Form splitting/Fieldset templatetag
Sectioned Form
Forms splitted in fieldsets
Edit: I just noticed this question again and I realize it needs an edit to point out Carl's project django-form-utils which contains a BetterForm class which can contain fieldsets. If you like this project give him a +1 for his answer below :)
One thing you can do is break your logical fieldsets into separate model form classes.
class PersonalInfoForm (forms.ModelForm):
class Meta:
model=MyModel
fields=('field1', 'field2', ...)
class TermsForm (forms.ModelForm):
class Meta:
model=MyModel
fields=('fieldX', 'fieldY', ...)
Pass them to your template in different variables and break up the formsets:
<form ...>
<fieldset><legend>Personal Information</legend>
{{ personal_info_form }}
</fieldset>
<fieldset><legend>Terms and Conditions</legend>
{{ terms_form }}
</fieldset>
</form>
In that sense each of your form classes is just a fragment of the actual HTML form.
It introduces a touch of complexity when you call save on the form. You'll probably want to pass commit=False and then merge the resultant objects. Or just avoid using ModelForm.save altogether and populate your model object by hand with 'cleaned_data'
Daniel Greenfelds django-uni-form solves this with a the Layout helper class. I'm trying it out right now and it looks pretty clean to me.
Uniform helpers can use layout objects. A layout can consist of fieldsets, rows, columns, HTML and fields.
I originally picked Django-uni-form because it complies with section 508.
You can use this package: https://pypi.org/project/django-forms-fieldset/
pip install django-forms-fieldset
Add forms_fieldset to your INSTALLED_APPS setting like this:
INSTALLED_APPS = [
...
'forms_fieldset',
]
Add fieldsets in your form
from django.forms import ModelForm
from .models import Student
class StudentForm(ModelForm):
fieldsets = [
("Student Information", {'fields': [
('first_name', 'last_name'),
('email', 'adress'),
]}),
("Parent Information", {'fields': [
'mother_name',
'father_name',
]}),
]
class Meta:
model = Student
fields = '__all__'
In your views
def home(request):
form = StudentForm()
if request.method == 'POST':
form = Form(request.POST, request.FILES)
#save...
context = {
'form': form,
}
return render(request, 'home.html', context)
in your template
{% load forms_fieldset static %}
<link rel="stylesheet" type="text/css" href="{% static 'forms_fieldset/css/main.css' %}">
<form>
{{ form|fieldset:'#42945c' }}
</form>
This was the code that I developed in order to understand custom tags (with links). I applied it to create a fieldset.
Disclaimer: I encourage the use of any of the above answers, this was just for the sake of learning.
templatetags/myextras.py:
from django import template
from django.template import Context
register = template.Library()
class FieldsetNode(template.Node):
""" Fieldset renderer for 'fieldset' tag """
def __init__(self, nodelist, fieldset_name):
""" Initialize renderer class
https://docs.djangoproject.com/en/1.8/howto/custom-template-tags/#writing-the-renderer
:param nodelist: a list of the template nodes inside a block of 'fieldset'
:param fieldset_name: the name of the fieldset
:return: None
"""
self.nodelist = nodelist
self.fieldset_name = fieldset_name
def render(self, context):
""" Render the inside of a fieldset block based on template file
https://docs.djangoproject.com/en/1.8/howto/custom-template-tags/#auto-escaping-considerations
:param context: the previous template context
:return: HTML string
"""
t = context.template.engine.get_template('myapp/fieldset.html')
return t.render(Context({
'var': self.nodelist.render(context),
'name': self.fieldset_name,
}, autoescape=context.autoescape))
#register.tag
def fieldset(parser, token):
""" Compilation function for fieldset block tag
Render a form fieldset
https://docs.djangoproject.com/en/1.8/howto/custom-template-tags/#writing-the-compilation-function
https://docs.djangoproject.com/en/1.8/howto/custom-template-tags/#parsing-until-another-block-tag
:param parser: template parser
:param token: tag name and variables
:return: HTML string
"""
try:
tag_name, fieldset_name = token.split_contents()
except ValueError:
raise template.TemplateSyntaxError("%r tag requires a single argument" % token.contents.split()[0])
if not (fieldset_name[0] == fieldset_name[-1] and fieldset_name[0] in ('"', "'")):
raise template.TemplateSyntaxError("%r tag's argument should be in quotes" % tag_name)
nodelist = parser.parse(('endfieldset',))
parser.delete_first_token()
return FieldsetNode(nodelist, fieldset_name[1:-1])
templates/myapp/fieldset.html:
<div class="fieldset panel panel-default">
<div class="panel-heading">{{ name }}</div>
<div class="panel-body">{{ var }}</div>
</div>
templates/myapp/myform.html:
<form action="{% url 'myapp:myurl' %}" method="post">
{% csrf_token %}
{% fieldset 'General' %}
{{form.myfield1 }}
{% endfieldset %}
{# my submit button #}
</form>