Reusable forms in Django app - django

Short:
I'd like to create an app which provides a well formatted form that can be imported into other apps. Doing this using nothing but ModelForm results in partial success as it renders only the form fields without any of the additionally required elements like buttons. This however should also be encapsulated in the app.
Long:
For a better understanding lets assume we have an app called blog and one called comments. They've been separated since comments may also be used in other places and apps. comments should provide a form which is then added to the view or the template of blog
So here's some pseudo code for better a understanding.
comments/models.py:
class Comment(models.Model):
comment = models.TextField()
author = models.CharField(max_length=64)
comments/forms.py:
class CommentForm(forms.ModelForm)
class Meta:
model = Comment
comments/templates/comments/comment_form.html:
<form method="post" action="#">
{{ formfields }}
<button type="submit">Submit</button>
</form>
Clearly there's a step missing since the ModelForm and the template are not brought together. My goal now is to import exacly such a marriage to blog
blog/views.py
def render_article(request):
context = {
...
comment_form: <SOMETHING THAT CREATES A NICE TEMPLATE BASED HTML FORM>
}
return render(request, 'blog/article.html', context)
In essence I'm looking for a single object of function that I can use in any kind of app to provide a complete form. That way it always looks the same in all apps.
How to do this?

You could override django.forms.Form.__unicode__ in your form:
from django.utils.safestring import mark_safe
class MyForm(forms.Form):
...
def __unicode__(self):
html = super(MyForm, self).__unicode__
html = html + '<button type="submit">Submit</button>'
// or whatever — render a wrapper template, anything
return mark_safe(html)

Related

Django & AJAX to show DB objects upon user's input submission

I'm pretty new in the Web development world, have been using Django so far.
I've been trying to figure out how to render data back to page after clicking on a submit button, so I see I'll need to use AJAX for that purpose.
I've created a very simple app just to understand the basics of AJAX.
However, googling, I couldn't really find a basic straight-forward implementation so I kinda got lost...
What I'm trying to achieve:
I have a model called Country:
class Country(models.Model):
name = models.CharField(primary_key=True, max_length=35)
continent = models.CharField(max_length=10)
capital = models.CharField(max_length=35)
currency = models.CharField(max_length=10)
And a super simple main page that asks the user to insert some country name.
The idea is to bring to the page all the info from the DB.
So it would look like this:
Main page HTML body:
<body>
<h2><em>Please type a country name:</em></h2><br><br>
<div class="container">
<form id="get_info" method="post">
{{ form }}
{% csrf_token %}
<input id="submit_button" type="submit" name="submit" value="Get info">
</form>
</div>
</body>
views.py:
from django.shortcuts import render
from country_trivia import forms
def main_page(request):
get_info_form = forms.GetInfo()
return render(request, 'country_trivia/index.html', {'form': get_info_form})
forms.py:
from django import forms
class GetInfo(forms.Form):
country_name = forms.CharField(label="")
I've seen some examples using forms, but I'm not even sure if it's needed, as I've seen some other examples that count on 'onclick' even listeners, then "grab" the text in the search field and pass it via AJAX...
How should I build my AJAX object for that simple purpose, and how should I integrate it?
Do I need to use forms at all?
I don't post anything to DB, just query it and print out data...
Thanks!!

Understanding Django and Django FormView

I am trying to create a Django web app that accepts text in a form/textbox, processes it and redirects to a webpage showing the processed text . I have written a half-functioning app and find de-bugging quite challenging because I don't understand most of what I've done. I'm hoping you will help me understand a few concepts, Linking to resources, also appreciated.
Consider this simple model:
class ThanksModel(models.Model):
thanks_text = models.CharField(max_length=200)
Is the only way to set the text of thanks_text through the manage.py shell? This feels like a pain if I just have one piece of text that I want to display. If I want to display a webpage that just says 'hi', do I still need to create a model?
Consider the view and template below:
views.py
class TestView(generic.FormView):
template_name = 'vader/test.html'
form_class = TestForm
success_url = '/thanks/'
test.html
<form action = "{% url 'vader:thanks'%}" method="post">
{% csrf_token %}
{{ form }}
<input type = "submit" value = "Submit">
</form>
I need to create another model, view and html template and update urls.py for '/thanks/' in order for the success_url to redirect correctly? (That's what I've done.) Do I need to use reverse() or reverse_lazy() the success_url in this situation?
Models are used when you are dealing with Objects and Data and DataBases that can contain a lot of information.
For Example A Person would be a model. their attributes would be age, name, nationality etc.
models.py
class Person(models.Model):
Name = models.CharField(max_length=50)
age = models.IntegerField()
nationality = models.CharField(max_length=50)
Thi deals with multiple bits of information for one object. (the object being the person)
A Thank you message would not need this? so scrap the model for the thank you message. just have views where you create the view using a templates and setting the view to a url.
views.py
class TestView(generic.FormView):
template_name = 'vader/test.html' # self explantory
form_class = TestForm # grabs the test form object
success_url = reverse_lazy('vader:thanks') # this makes sure you can use the name of the url instead of the path
def ThanksView(request): # its simple so you don't even need a class base view. a function view will do just fine.
return render(request,"thanks.html")
test.html
<form action = "{% url 'vader:thanks'%}" method="post">
{% csrf_token %}
{{ form }}
<input type = "submit" value = "Submit">
</form>
thanks.html
<h1>Thank you for Submitting</h1>
<h2> Come Again </h2>
url.py
from django.urls import path
from djangoapp5 import views
urlpatterns = [
path('', TestView.as_view(), name='test_form'),
path('thanks/', views.ThanksView, name='vader:thanks'),
]
I haven't tested this but hopefully it helps and guide you in the right direction

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.

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

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>

Using class based generic view DetailView with a ModelForm reveals a bug - how to proceed?

I've been impressed how rapidly a functional website can go together with generic views in the tutorials. Also, the workflow for form processing is nice. I used the ModelForm helper class to create a form from a model I made and was delighted to see that so much functionality came together. When I used the generic list_detail.object_detail I was disappointed that all that I could display were fields individually. I knew the ModelForm class contained information for rendering, so I wanted to use the ModelForm with a generic view.
I was asking around on stackoverflow to get some direction, and appreciate the answers and comments from several posters. I've figured out how to get this to work, but there is a bug in DetailView. The solution includes a workaround.
To use a ModelView with the generic view and get all the fields to render automatically the following works:
Create a project, and in it create application inpatients.
If you have
# inpatients/models.py
class Inpatient(models.Model):
last_name = models.CharField(max_length=30)
first_name = models.CharField(max_length=30,blank=True)
address = models.CharField(max_length=50,blank=True)
city = models.CharField(max_length=60,blank=True)
state = models.CharField(max_length=30,blank=True)
DOB = models.DateField(blank=True,null=True)
notes = models.TextField(blank=True)
def __unicode__(self):
return u'%s, %s %s' % (self.last_name, self.first_name, self.DOB)
class InpatientForm(ModelForm):
class Meta:
model = Inpatient
and
# inpatients/views.py
from django.http import HttpResponse, HttpResponseRedirect
from django.shortcuts import render_to_response
from django.views.generic import DetailView
from portal.inpatients.models import *
def formtest(request):
if request.method == 'POST':
form = InpatientForm(request.POST)
if form.is_valid():
form.save()
return HttpResponseRedirect('/inpatients')
else:
form = InpatientForm()
return render_to_response("formtest.html", {'form': form})
class FormDetailView(DetailView):
model=Inpatient
context_object_name='inpatient' # defines the name in the template
template_name_field='inpatient_list_page.html'
def get_object(self):
inpatient=super(FormDetailView,self).get_object()
form=InpatientForm(instance=inpatient)
return form
def get_template_names(self):
return ['inpatient_list_page.html',]
and
#urls.py
from django.conf.urls.defaults import patterns, include, url
from django.views.generic import ListView
from portal.inpatients.models import Inpatient, InpatientForm
from portal.inpatients.views import FormDetailView
urlpatterns = patterns('',
(r'^formtest/$','portal.inpatients.views.formtest'),
(r'^inpatients/$', ListView.as_view(
model=Inpatient, template_name='inpatient_list_page.html')),
(r'^inpatient-detail/(?P<pk>\d+)/$', FormDetailView.as_view()),
)
# with a template containing
{% block content %}
<h2>Inpatients</h2>
<ul>
{% for aninpatient in object_list %}
<li><a href='/inpatient-detail/{{ aninpatient.id }}/'>
{{ aninpatient }}, id={{ aninpatient.id }}</a></li>
{% endfor %}
</ul>
{{ inpatient.as_p }}
{% endblock %}
# Yeah, kind of hokey. The template is for both the list view and detail view.
# Note how the form is rendered with one line - {{ inpatient.as_p }}
it works. The instructions for using class based generic views lives at https://docs.djangoproject.com/en/1.3/topics/class-based-views/ Instructions there are pretty clear. The key to making things work is to redefine get_object. In the documentation under the section "Performing extra work" it nicely describes how to do this, the steps being to call the original version of get_object, and then to the extra work. The bit that I realized is that the return object can be a ModelForm object. The object that get_object returns goes straight into the template in a render. By taking the retrieved inpatient object and running it through InpatientForm it can be passed to a view as a form which then renders itself.
As to the bug: The bug in DetailView is that the get_template_names function tries to make a template name from a structure that does not exist. In
https://code.djangoproject.com/browser/django/trunk/django/views/generic/detail.py
on lines 127 to 140 we have within SingleObjectTemplateResponseMixin.get_template_names:
127 # The least-specific option is the default <app>/<model>_detail.html;
128 # only use this if the object in question is a model.
129 if hasattr(self.object, '_meta'):
130 names.append("%s/%s%s.html" % (
131 self.object._meta.app_label,
132 self.object._meta.object_name.lower(),
133 self.template_name_suffix
134 ))
135 elif hasattr(self, 'model') and hasattr(self.model, '_meta'):
136 names.append("%s/%s%s.html" % (
137 self.model._meta.app_label,
138 self.model._meta.object_name.lower(),
139 self.template_name_suffix
140 ))
The error is that the code on line 131 is executed and dies with error message <'ModelFormOptions' object has no attribute 'app_label'>. I conclude that the _meta object is defined. I suppose that the problem is that in a ModelForm the class Meta is defined. That Meta probably doesn't have the fields set that are expected. The workaround is just to rewrite get_template_names and return the correct template.
I'm new to Django and Python. I appreciate the answers and comments by the contributors at the following previous questions I asked. (
Putting links in list_detail.object_list to list_detail.object_detail,
Using form in object_detail,
Rolling your own generic views in Django)
What should I do to report the bug?
You are right I believe. This is a bug which stems from the fact that both ModelForm and Models have a _meta attribute. This same bug would exhibit itself anytime an object is returned from get_object() that contains a _meta attribute.
get_object does not have to return a Model instance. You can confirm this by looking at the source for DetailView and reading it's docstring:
class DetailView(SingleObjectTemplateResponseMixin, BaseDetailView):
"""
Render a "detail" view of an object.
By default this is a model instance looked up from `self.queryset`, but the
view will support display of *any* object by overriding `self.get_object()`.
"""
Notice that the doc string explicitly says that any object is supported by overriding self.get_object().
Another piece of corroborating evidence is from the location where this bug itself occurs which is the get_template_names method of SingleObjectTemplateResponseMixin.
# The least-specific option is the default <app>/<model>_detail.html;
# only use this if the object in question is a model.
if hasattr(self.object, '_meta'):
names.append("%s/%s%s.html" % (
self.object._meta.app_label,
self.object._meta.object_name.lower(),
self.template_name_suffix
))
elif hasattr(self, 'model') and hasattr(self.model, '_meta'):
names.append("%s/%s%s.html" % (
self.model._meta.app_label,
self.model._meta.object_name.lower(),
self.template_name_suffix
))
Again looking at this code, the comment itself say "If the object in question is a model". From this comment we can infer that the object doesn't always have to be a model.
However if you are trying to create a view that allows someone to edit/create/delete a model you really should have a look at the Editing Views which include FormView, CreateView, EditView and DeleteView. You can see more information for these at https://docs.djangoproject.com/en/1.3/ref/class-based-views/#editing-views.
To answer the question as to how to report the bug, you should follow the guidelines detailed at https://docs.djangoproject.com/en/1.3/internals/contributing/#reporting-bugs.