1

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...

EmeraldOwl
  • 315
  • 1
  • 4
  • 9

1 Answers1

3
  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.

misraX
  • 887
  • 5
  • 15
professorDante
  • 2,290
  • 16
  • 26
  • Thanks for your answer. How do I get the Form to the template? I originally wrote a Form and a view like usual, but the form doesn't render that way. How do I pass the form to the template? – EmeraldOwl Sep 21 '13 at 19:29
  • Can you post your current view that isn't working and we can take a look. – professorDante Sep 21 '13 at 19:41
  • Thanks for updating your answer. The form is rendering correctly now, but I'm not sure how to handle errors. I guess I need my send_message_view to render the updated form to the same template as the user was currently viewing (places/categories.html in my example). Is there a way I can pass the current template name to the view as a URL parameter? – EmeraldOwl Sep 22 '13 at 10:35
  • OK, that worked, but I can't redirect from the template tag - I've added my updated code to the question. Thanks again for your help, this is a tough problem but it would be awesome if we could make it work. – EmeraldOwl Sep 23 '13 at 08:47
  • The redirect does work - I've done it successfully in my dev environment. The tag function only takes 'context' as an arg when you set takes_context=True. You've passed in extra args. Set them in the context variable to access them correctly. Good luck! – professorDante Sep 23 '13 at 16:18
  • I didn't think I had to put them all in the context - it uses them to make the form without any problems. And the tag does have takes_context=True, it's the last thing on the decorator so you'd have to scroll right to get to it. Like I said, everything else works apart from the redirect, and I haven't seen any examples of redirect working from an inclusion tag. – EmeraldOwl Sep 23 '13 at 16:47
  • You're right, my bad. You can pass extra args no problem, even with context=True. – professorDante Sep 23 '13 at 19:31
  • So any idea why the redirect isn't working? I saw [this question](http://stackoverflow.com/questions/6713022/handling-request-in-django-inclusion-template-tag) which has the same kind of code as mine, and I guess there's worked, but mine doesn't. I think it returns the redirect to the template, as if it's the content. – EmeraldOwl Sep 23 '13 at 20:22
  • You know what, you solved my original problem of getting the form to render on multiple pages, maybe I should ask a new question about the redirect? – EmeraldOwl Sep 23 '13 at 20:36
  • OK, LAST bit of help added! – professorDante Sep 23 '13 at 20:40
  • Sadly the product owner wants a redirect after sending a message, but I'll accept your answer as you've been a great help. – EmeraldOwl Sep 23 '13 at 20:54
  • Use your new found understanding of inclusion tags and messaging to convince them otherwise. Good luck! – professorDante Sep 23 '13 at 20:57
  • I've used this same solution but found a problem displaying the message. Posted question, would love any assistance - http://stackoverflow.com/questions/28124348/delayed-display-of-message-from-within-an-inclusion-tag – Bosco Jan 24 '15 at 12:30