Dynamically get template name in bound Django form - django

I am trying to render a Django contact form on any arbitrary page. I am doing it with a request context processor and a template include. This allows me to display the form fine anywhere I want. Then I have a special URL that accepts POST requests (on GET, I just redirect them). If the form is valid, I send an email, and redirect to a success page. On form invalid, I know to pass the form bound with errors, but...I don't know which template to specify because the form is an include and the parent template could be anywhere.
The only way to get something in a Django view is from the request. I can get the path, and with more work, probably the original view from where the POST came from, but that doesn't get me the template.
# urls.py
url(r'^services/$', 'website.views.services', name='services'),
url(r'^services/contact/$', 'website.views.services_contact', name='services_contact'),
url(r'^services/contact/done/$', 'website.views.services_contact_done', name='services_contact_done')
# views.py
class ServicesView(TemplateView):
template_name = 'services/services.html'
services = ServicesView.as_view()
class ServicesContactView(View):
def get(self, request, *args, **kwargs):
return redirect('services')
def post(self, request, *args, **kwargs):
form = ContactForm(request.POST)
if form.is_valid():
form.send_email()
return redirect('services_contact_done')
else:
return render(request, ????, {'contact_form': form})
services_contact = ServicesContactView.as_view()
# contact.html
<h2>Contact me</h2>
<p>Enter your email to receive your questionnaire</p>
<form action="{% url 'services_contact' %}" method="post">
{% csrf_token %}
{% if contact_form.non_field_errors %}
{{ contact_form.non_field_errors }}
{% endif %}
{{ contact_form.as_p }}
<button type="submit" name="submit">Send questionnaire</button>
</form>
# home.html
{% extends "base.html" %}
{% block content %}
<h1>{{ site.name }}</h1>
{% include "services/contact.html" %}
{% endblock %}
The typical Django form view is somewhat silent on form invalid in that its scenario is mostly similar to an unbound form, so it's all just render in the end. My scenario is different due to the template include.

You could set up a session variable every time you render a template and use it afterwards when you need it :
request.session['template']="nameOfTemplate"
.
return render(request, request.session.get('template', 'default.html'), {'contact_form': form})
I know it requires to write a line of code every time you render a template, but that's the best solution I could think of.

If anybody needs this answer, I figured it out on my own. It's possible, but a different approach is required. First, a request context processor is not appropriate for this situation. They're fairly dumb because they just get something once and stick it in the context. Their only advantage is their global nature.
My context processor:
def contact_form(request):
"""
Gets the contact form and adds it to the request context.
You almost certainly don't want to do this.
"""
form = ContactForm()
return {'contact_form': form}
The nature of forms is that they act differently after being processed by Django's validation machinery, specifically ContactForm() is an unbound form and will always be. You don't want to do this (unless you want a form that simply displays but doesn't work). The TEMPLATE_CONTEXT_PROCESSORS should be edited to remove this processor.
Now the burden on displaying the form is back on the view, which also means just about any view must be able to handle POST requests as well. This means that editing each view that wants a contact form is required, but we can use the power of class-based views and mixins to handle most of the repetition.
ServicesView remains almost the same as a TemplateView, except with a mixin that will handle the form. This way, the template name always remains the same (my original problem), but with additional form power.
class ServicesView(ContactMixin, TemplateView):
template_name = 'services/services.html'
services = ServicesView.as_view()
ContactMixin uses FormMixin, to create and display a form, and ProcessFormView to handle the GET and POST requests for the form. And because the form's nature changes with different kinds of requests (unsubmitted, submitted and invalid, submitted and valid), get_context_data needs to be updated with the correct form class instance. Lastly, we probably want to prefix (namespace) our form because it can can be used anywhere, and we want to avoid conflicts when another possible form can POST to the same view. Thus, the mixin is:
class ContactMixin(FormMixin, ProcessFormView):
form_class = ContactForm
success_url = reverse_lazy('contact_done')
def get_form_kwargs(self):
kwargs = super(ContactMixin, self).get_form_kwargs()
kwargs['prefix'] = 'contact'
return kwargs
def get_context_data(self, **kwargs):
context = super(ContactMixin, self).get_context_data(**kwargs)
form_class = self.get_form_class()
context['contact_form'] = self.get_form(form_class)
return context
def form_valid(self, form):
form.send_email()
return super(ContactMixin, self).form_valid(form)
The subtleties of self.get_form_class() were almost lost on me if it were not for an example in the docs (of what not to do, heh) and another StackOverflow answer, where I would've usually just said self.form_class, which ignores the processing of the form.
Now I simply add ContactMixin to any view and {% include "includes/contact.html" %} to any template.

Related

How to reuse Django's admin foreign key widget in admin intermediate pages

I haven't been able to find the answer anywhere on Django's documentation. Though, I'm not surprised given the question is a bit too complex to ask to a search engine.
I'm in a situation where I need to be able reassign a ForeignKey field for one or more entries of a model on Django's admin site.
So far, what I tried to do so using a custom action so that I can select the records I'm interested in and modify them all at once. But, then, I need to select the new related object I want their fk to be reassigned to. So, what I thought to do is an intermediate page where I'd display the fk widget I see all around the admin pages:
But it turns out this widget is really not designed to be publicly used. It's not documented and it's heavily complex to use. So far, I lost several hours digging into Django's code trying to figure how to use it.
I feel like I'm trying to do something really really exotic here so, if there's another solution, I'm all hears.
As shahbaz ahmad suggested, you can use ModelAdmin's autocomplete_fields which creates an select with autocompletion.
But if you're stuck with Django's foreign key widget, because, for instance, you have records which look the same and are indistinguishable in autocomplete, there is a solution.
It turns out ModelAdmin has a get_form method that you can use to retrieve the ModelForm used on the admin page. This method accepts a fields kwargs that you can use to select the fields you want to retrieve in the form. Use it like this:
class MyAdmin(ModelAdmin):
# define the admin subpath to your intermediate page
def get_urls(self):
return [
path(
"intermediate_page/",
self.admin_site.admin_view(self.intermediate_page),
name="intermediate_page",
),
*super().get_urls(),
]
def intermediate_page(self, request):
context = {
# The rest of the context from admin
**self.admin_site.each_context(request),
# Retrieve the admin form
"form": self.get_form(
request,
fields=[], # the fields you're interested in
)
}
return render(request, "admin/intermediate_page.html", context)
If your field is read only – which was my case – there's a workaround to an editable field: you can override the get_readonly_fields method which is called by get_form.
This method accepts an obj parameter which usually takes the model of the object being edited or None when creating a new entry. You can hijack this parameter to force get_readonly_fields exclude fields from read only fields:
def get_readonly_fields(self, request, obj=None):
readony_fields = super().get_readonly_fields(request, obj)
if not (
isinstance(obj, dict)
and isinstance(obj.get("exclude_from_readonly_fields"), Collection)
):
return readony_fields
return set(readony_fields) - set(obj["exclude_from_readonly_fields"])
get_form also has this obj parameter which it passes down to get_readonly_fields so you can call it like this:
# the fields you're interested in
include_fields = []
self.get_form(
request,
obj={"exclude_from_readonly_fields": include_fields},
fields=include_fields
)
Override changelist template of YourModelAdmin class to add one more button apart from add button.
#admin.register(YourModel)
class YourModelAdmin(admin.ModelAdmin):
change_list_template = "custom_your_model_change_list.html"
In custom_your_model_change_list.html,
{% extends "admin/change_list.html" %}
{% block object-tools-items %}
<li>
<a class="button" href="{% url 'your_reassign_url_name' %}">Reassign</a>
</li>
{{ block.super }}
{% endblock %}
Mapped a view to 'your_reassign_url_name' to processed your request.
In urls.py,
urlpatterns = [
path('reassign/', YourReassignView, name='your_reassign_url_name'),
]
forms.py,
class ReassignForm(forms.Form):
# your reassign field
reassign_field = forms.ModelChoiceField(queryset='your queryset')
# Select multiple objects
updatable_objects = forms.ModelMultipleChoiceField(queryset='All objects queryset',
widget=forms.CheckboxSelectMultiple)
In views.py, On GET request you render a form with your required fields and on submit your update your data (reassign values) and after that you redirect admin change_list page.
def YourReassignView(request):
if request.method == 'POST':
form = ReassignForm(request.POST)
if form.is_valid():
# you get value after form submission
reassign_field = form.cleaned_data.get('reassign_field')
updatable_objects = form.cleaned_data.get('updatable_objects')
# your update query
YourModel.objects.filter(
id__in=updatable_objects.values('id')
).update(field_name=reassign_field)
#after that you redirect admin change_list page with a success message
messages.success(request, 'Successfully reassign')
return redirect(reverse('admin:app_label_model_name_changelist'))
else:
context_data = {'form': form}
return render(request, 'reassign_template.html', context=context_data)
else:
form = ReassignForm()
context_data = {'form': form}
return render(request, 'reassign_template.html', context=context_data)
In reassign_template.html,
<form method="POST" action="{% url 'your_reassign_url_name' %}">
{% csrf_token %}
{{ form.as_p }}
<br>
<input type="submit" value="submit">
</form>

The "post" method in Django

I created three files:
2- view.py :
class AddTeamView(View):
def get (self, request):
form = TeamForm()
context = {'form': form}
return render(request, 'add_team.html', context)
1-forms.py:
class TeamForm(forms.Form):
name = forms.CharField( max_length='100')
details = forms.CharField(max_length='250')
3-add_team.html:
-here there is another file called "base.html"
{% extends 'base.html' %}
{% block title %}
add team
{% endblock %}
{% block content %}
<form action="/add_team/" method="post">
{% csrf_token %}
{{ form }}
<input type="submit" value="Submit">
</form>
{% endblock %}
and i went to cmd and entered the server "python manage.py runserver"
it appeared on the browser:
"This page isn’t working
If the problem continues, contact the site owner.
HTTP ERROR 405"
A view can support methods like GET, POST, PUT, etc. given the corresponding method exists, so the view should have a .get(..), .post(..), .put(..), etc. function.
Here you only implemented a def get(self, request), and so POST requests are not allowed.
Based on the data you however show, this looks like the typical usecase of a CreateView [Django-doc]. The idea of these views is to encapsulate common scenario's such that by overriding a few attributes, one creates a view that is tailored towards a specific case, like:
class AddTeamView(CreateView):
form_class = TeamForm
template_name = 'add_team.html'
success_url = '/some/success_url'
The TeamForm should probably be a ModelForm, or at least a Form where you override the .save(..) function to properly save your data to the database, since right now, the form is not doing anything (well it receives the data, but after validation, it throws it away).
You might want to override the form_valid(..) function in case you do not want to redirect to the success_url. Furthermore it is very common that the success_url is resolved lazily from a given view name, like:
class AddTeamView(CreateView):
form_class = TeamForm
template_name = 'add_team.html'
success_url = reverse_lazy('view_name')
So, we don’t need to do a conditional to check if the request is a POST or if it’s a GET:
Your views.py:
from django.views.generic import View
class AddTeamView(View):
def post(self, request):
form = TeamForm(request.POST)
if form.is_valid():
new_tm = TeamModel(name=form.cleaned_data['name'], details=form.cleaned_data['details'])
new_tm.save()
return redirect('team_list')
return render(request, 'add_team.html', {'form': form})
def get(self, request):
form = TeamForm()
return render(request, 'add_team.html', {'form': form})
Hope this help you...

Auto-populate Django template with context variable passed from a Django view (no js)

I have Django template which has one CharField. Users can enter a mobile phone number here and submit it.
The view connected to this template sometimes passes to the said template, a context variable containing a pre-defined phone number {{ number }}.
How do I auto-populate the CharField in the Django template with {{ number }}?
The template code is like so:
<form action="{% url 'user_phonenumber' slug=unique num=decision %}" method="POST">
{% csrf_token %}
<b style="font-size:90%;color:green;">Enter your mobile number:</b><br><br>
{{ form.mobile_number }}<br><br>
<input type="submit" name="number" value="OK"><br>
</form>
If required, forms.py contains the following:
class UserPhoneNumberForm(forms.Form):
mobile_number = forms.CharField(max_length=50)
class Meta:
fields = ("mobile_number")
In views.py, I simply have:
class UserPhoneNumberView(FormView):
form_class = UserPhoneNumberForm
template_name = "get_user_phonenumber.html"
def get_context_data(self, **kwargs):
context = super(UserPhoneNumberView, self).get_context_data(**kwargs)
if self.request.user.is_authenticated():
#some code to determine what number should be
context["number"] = number
return context
I've looked at a few other SO answers. The last one I've linked comes close to solving the problem, but the solution isn't for dynamic values (which is what I need), plus that's for modelforms. The rest don't quite address my particular needs.
I needed to over-ride def get_initial(self): of my FormView to achieve this. I.e. instead of passing a context variable to the template, pass the variable as initial through get_initial() to the related form.
Here's how I did it:
def get_initial(self):
"""
Returns the initial data to use for forms on this view.
"""
if self.request.user.is_authenticated():
#What's the latest chatpicmessage which houses a pic you own
try:
msg = PicMessage.objects.filter(which_pic__owner=self.request.user).latest('sending_time')
self.initial = {'mobile_number': msg.what_number} #initial needs to be passed a dictionary
return self.initial
except:
return self.initial
else:#i.e user is not authenticated
return self.initial
return self.initial
More documentation regarding this can be found here: https://docs.djangoproject.com/es/1.9/ref/forms/fields/#initial

Mutiple forms with Class Based View. How to show errors at the same page?

views.py
from forms.py import PersonCreateForm
class PersonCreateView(CreateView):
model = Person
form_class = PersonCreateForm
template_name = "my_app/create_person.html"
def form_valid(self, form):
self.object = form.save()
return redirect('/homepage/')
class PeopleListView(ListView):
[...]
context.update({
'task_form': TaskCreateForm(),
return context
In my template I just add in action url which handle PersonCreateView.
<form action="{% url 'people_create' %}" method="post">{% csrf_token %}
When Form is valid then all data are saved without problems and it redirects me to '/homepage/.
But when my form is invalid then it redirects me to to {% url 'people_create' %} and shows errors at /homepage/people_create/
How can I avoid that? I want all errors show at same page without redirect.
Handle the form on the same view you build it, otherwise the page will change. You may mix django.views.generic.edit.ModelFormMixin into your PeopleListView so it has most of the features you need.
class PeopleListView(ModelFormMixin, ListView):
success_url = '/homepage/' # should use reverse() here
def get_context_data(self, **kwargs):
# only add the form if it is not already given to us
if not 'task_form' in kwargs:
kwargs['task_form'] = self.get_form()
return super(PeopleListView, self).get_context_data(**kwargs)
def post(self, request, *args, **kwargs):
# ListView won't have a post method, we define one
form = self.get_form()
if form.is_valid():
return self.form_valid(form) # default behavior will save and redirect
else:
return self.form_invalid(form) # default behavior has to be overridden (see below)
def form_invalid(self, form):
# Whatever you wanna do. This example simply reloads the list
self.object_list = self.get_queryset()
context = self.get_context_data(task_form=form)
return self.render_to_response(context)
There, you have three code paths:
Initial display will load the listview as usual, only an empty form will be added to the context.
On submitting valid input, the form_valid method is invoked, which will redirect to /homepage/.
On submitting invalid input, our overridden form_invalid method is invoked, which will render the page normally, except the form will contain the validation errors.
You may make the whole thing a bit more staightforward using a cached property for the form, but then you'd start working against Django's shipped views instead of with it, and might as well just use the basic View class and implement all logic by yourself. I'd stick with Django's views, but ymmv.

Django Class Based Views with popup functionality

I wanted to ask if the following implementation is rational against Django rules of Views and Class Based views.
The scenario, I have implemented a mini administration section for users, not wanting to grant access to the general admin section of Django.
I am ok with the implementation and everything is working properly, recently I added also a small mixin to allow users to add related items the django admin way, the mixin is working properly (based on the original mixin of the Django admin interface). Just wanted to ask if this is a good approach or should I move to another implementation? (this is still a bit rough, made just for tests)
The mixin, we are actually checking against a query args, _popup, this is nearly the same way django admin checks for passed popups, this allows the form to load normally if working on it directly or as a popup when called through the parent form. partials/popup_reponse.html
class PopupMixin(object):
is_popup = False
popup_var = '_popup'
def get_context_data(self, **kwargs):
''' Add the _popup to the context, so we can propagade the templates'''
context = super(PopupMixin, self).get_context_data(**kwargs)
if self.popup_var in self.request.GET:
self.is_popup = True
context['is_popup'] = self.is_popup
return context
def form_valid(self, form):
if self.popup_var in self.request.POST:
self.is_popup = True
''' If this is a popup request, then we are working with a form
this means we save the form, and then override the default form_valid implementation
this allows us to inject newly created items in the form '''
if self.is_popup:
self.object = form.save()
return SimpleTemplateResponse('partials/popup_response.html', {
'value': self.object.id,
'obj': self.object
})
return super(PopupMixin, self).form_valid(form)
Then our create:
class TestCreateView(PopupMixin,CreateView):
form_class = TestForm
template_name = "add.html"
success_url = reverse_lazy('manager-list-tests')
This way in the view, I can make sure that first the base template does load only a bare form and not any other template parts, additionally I get to add a hidden field in the form:
{% if is_popup %}
<input type="hidden" name="_popup" value=1>
{% endif %}
Through this I override the default form_valid to respond with the same template as django admin uses:
<!DOCTYPE html>
<html>
<head><title></title></head>
<body>
<script type="text/javascript">
opener.dismissAddAnotherPopup(window, "{{ value }}", "{{ obj }}");
</script>
</body>
</html>