EmeraldOwl
EmeraldOwl

Reputation: 325

How to include a form in multiple templates

I have a Django app with two types of users, lets call them A and B. The two user types have different navigation bars. User type A has a link in their navigation bar to allow them to send a message. The send message form comes up in an overlay, so the form needs to be present in every template, or fetched over AJAX. These users can send a message from any/every page on the site, and each page has its own view. If a message has errors, users should be returned to the same page they were on, and errors should be shown in the form overlay. If it is sent successfully, they should be redirected to a new page that shows their message.

Currently, I have put the form into a separate template that is included on all pages for user type A. I specified the form action as a view called send_message, and I have told that view to render to this template. The view is never called, so I guess this isn't the right way to do it.

How can I render this form in every template? Do I need to call a function from each view, or use a special template tag (I looked at inclusion tags, but I don't think they're right for this situation)? Or should I fetch the form using AJAX? Do I add the form view to every URL?

EDIT: Adding some code to explain.

Say, for example, I am on a page with a simple view like this:

@login_required
def index_view(request, place_id):
    categories = Category.objects.all()
    return render(request, 'places/categories.html', {'place_id': place_id, 'categories': categories})

The template for this page is extended from a template with the nav bar in it, and that template includes the send a message form, which looks like this:

<div id="message-popup">
    <h1>NewMessage</h1>
        <form action="{% url "send_message" %}" method="post">
            {% csrf_token %}
            {% get_message_form as message_form %}
            <table>
                <tr>
                    <td>{{ message_form.recipient.label_tag }}{{ message_form.recipient }}</span></td>
                    <td>{{ message_form.title.label_tag }}{{ message_form.title }}</td>
                </tr>
            <tr>
                <td>{{ message_form.categories.label_tag }}{{ message_form.categories }}</td>
                <td>
                    {{ message_form.content.label_tag }}{{ message_form.content }}
                    <button type="submit" class="button">Send</button>
                    <button class="box-close button">Cancel</button>
                </td>
            </tr>
        </table>
    </form>
</div>

That form posts to this view:

def send_message_view(request, place_id):
    place = Place.objects.get(id=place_id)
    if request.POST:   
        user = get_user_class(request.user)
        message_form = forms.SendMessageForm(request.POST, place=place, user=user)
        if message_form.is_valid():
            message_form.save()
            # redirect to page showing the message
    else:
        message_form = forms.SendMessageForm(place=place, user=user)
    return render(request, 'messaging/send_message_form.html', {'message_form': message_form})

The step I'm missing is how to get the form to render in the first place. When I go to the categories page, the form isn't there - obviously because there is no message_form variable in the context. So can I get my send_message_view and my index_view to both run and render the two templates, or is there some other way I have to link it all together?

EDIT:

OK, here is my template tag now:

@register.inclusion_tag('messaging/send_message_form.html', name='get_message_form', takes_context=True)
def get_message_form(context, place, sender, recipient):
    message_form = SendMessageForm(context['request'].POST or None, place=place, sender=sender, recipient=recipient)
    url = None
    if context['request'].method=='POST' and message_form.is_valid():
        message = message_form.save()
        url = reverse('conversation', kwargs={'place_slug': place.slug, 'message_id': message.id})
        """ This doesn't work
        return HttpResponseRedirect(url) """
    return {'message_form': message_form, 'place_id': place.id, 'message_sent_url': url}

I can't do an HttpResponseRedirect from the template tag, so I need to find some other way of redirecting to the success page. I can send the redirect URL on to the template, but I don't know what I can do with it from there...

Upvotes: 1

Views: 3263

Answers (1)

professorDante
professorDante

Reputation: 2405

  1. You have two choices - place the form in your base.html template file, and make every template inherit from that, or make a separate template file, as you have done at the moment, and {%include%} it wherever you want it.
  2. Not sure what you mean by 'call a function from each view'. A view is a function or a class.
  3. The AJAX functionality is a separate issue - get the form rendering as you want it first, with all your inheritance working with a synchronous call. Then implement the AJAX if you want it.
  4. Your form will go to the url you specify in that form, so only that view function will need the form logic. UPDATE:

Looks like you want a custom inclusion tag to render your Form.

@register.inclusion_tag('<your-form-render-template.html>')
    def get_message_form(place_id, user):
        return {'message_form': SendMessageForm(Place(id=place_id), user)}

And then in your index.html template:

{%load my-template-tags%}

<div id="message-popup">
    <h1>NewMessage</h1>
    {%get_message_form place_id request.user%}
</div>

Your template file you registered with your tag then renders the form:

    <form action="{% url "send_message" %}" method="post">
        {% csrf_token %}
        <table>
            <tr>
                <td>{{ message_form.recipient.label_tag }}{{ message_form.recipient }}</span></td>
                <td>{{ message_form.title.label_tag }}{{ message_form.title }}</td>
            </tr>
        <tr>
            <td>{{ message_form.categories.label_tag }}{{ message_form.categories }}</td>
            <td>
                {{ message_form.content.label_tag }}{{ message_form.content }}
                <button type="submit" class="button">Send</button>
                <button class="box-close button">Cancel</button>
            </td>
        </tr>
    </table>
</form>

Now anywhere you want to put your form, just load your tag and pass in the correct args to the template tag. Boom!

There are specific requirements for adding tags, such as app structure etc. Read up on it here.

UPDATE:

OK, this is cool. You can add in the context variable into your inclusion tag. Now your tag looks like this:

@register.inclusion_tag('<your-form-render-template.html>', takes_context=True)
    def get_message_form(context):
        message_form = forms.SendMessageForm(context['request'].POST or None, place=context['place_id'], user=context['request.user'])
        if context['request'].method=='POST' and message_form.is_valid():
            #redirect to thanks

        return {'message_form': SendMessageForm(Place(id=place_id), user)}

Now your inclusion tag serves the same purpose as your old form view function. Your form can now just have an action ".", which wont need to perform any logic on the form, the tag does it.

Note you have an error in your template - you need to explicitly show the form errors with {{message_form.errors}}.

Thanks for this excellent question - never done this before, and it's super powerful.

FINAL UPDATE!!!

OK, last bit of help for you, then you're on your own :-) It's probably not working on the redirect as this inclusion tag just returns the updated context variable to display. In other words, you cant redirect once you get here. So, as we only want to display a message to the user saying 'you were successful', lets use the messaging framework.

@register.inclusion_tag('messaging/send_message_form.html', name='get_message_form', takes_context=True)
def get_message_form(context, place, sender, recipient):
    from django.contrib import messages

    message_form = SendMessageForm(context['request'].POST or None, place=place, sender=sender, recipient=recipient)
    url = None
    if context['request'].method=='POST' and message_form.is_valid():
        message = message_form.save()
        messages.add_message(context['request'], messages.INFO, "Dante Rules!")

    return {'message_form': message_form, 'place_id': place.id, 'message_sent_url': url}

Now that message will be available in your main template, so you can check for it after you render the form with the inclusion tag:

    {% for message in messages %}
        <li{% if message.tags %} class="{{ message.tags }}"{% endif %}>{{ message }}</li>
    {% endfor %}

Once it's been rendered, it disappears from context, which is perfect for our case. So there you go, you can display it in a JQuery UIDialog, another part of the page, or whatever. It looks better than a re-direct IMHO anyways, as you stay on the same page you sent the message from.

Upvotes: 3

Related Questions