1
votes

i'm quite new using django and i've been stuck in this problem for several days. I have a form.Form in a bootstrap modal on my template with only 1 field (email_field) and basically i need to submit that form via ajax, check if that email address is registered in the database, then send an invitation to that email and close the modal. if the email is not registered show the form errors without closing the modal. I've tried with different examples but can find the solution either because the examples don't handle errors or the form is not inside a modal or not using class based views

.

I'm having 2 issues with my code:

  1. Not sure what to return in my view if the form is valid or invalid and how to handle errors in my js code to show them on the modal.(return tha form to render the errors or a JSON response??).
  2. After the first success submission is made the form cannot be used again.(The size of the submit button changes and if you click it return a error : CSRF token missing or incorrect)

Form.py

class CollaboratorForm(forms.Form):
email_address = forms.EmailField(required=True,widget=forms.TextInput(attrs={'class': 'form-control focus-text-box', 'type': 'email',
     'placeholder': 'Enter email'}))

def clean_email_address(self):
    email = self.cleaned_data['email_address']
    if not User.objects.filter(email=email):
        raise forms.ValidationError('This user is not registered')
    return email

def sendEmail(self, datas):
    message = "Hello, " + datas['user_name']+" "+ datas['email_from'] + " invited you to collaborate in an existing project. Follow this link if you are interested " + datas['invitation_link']
    msg = EmailMessage('Invitation from ' + datas['user_name'],
                   message, to=[datas['email_to']])      
    msg.send()

Template.html (project_detail.html)

<script src="{% static '/experiments/js/invite_collaborator.js' %}"></script>

<div class="bootstrap-modal modal fade in" id="collaboratorModal" style="display: none;">
    <div class="modal-body">
    <form  action="{% url 'experiments:invite-collaborator' project_id=project.id %}" method="post" id=collaborator-form >
      {% csrf_token %}

    <div class="form-group">
    {% if collaborator_form.errors %}
        <ol>
        {% for error in collaborator_form.errors %}
            <li><strong>{{ error|escape }}</strong></li>
        {% endfor %}
        </ol>
    {% endif %}

    <label class="control-label">Invite someone by email</label>
    <div class="input-group mt10">
    {{ collaborator_form }}
    <span class="input-group-btn">
    <input name="collaborator-commit" onClick="invite({{project.id}});" class="btn btn-primary" data-disable-with="Send Invitation" id="invite-button" type="submit">
    </span>
    </div>
    </div>
    </form>
    </div>
    </div>

Url.py

urlpatterns = [
    url(r'^(?P<project_id>[0-9]+)/invite_collaborator$', views.InviteCollaborator.as_view(), name='invite-collaborator'),
]

View.py

class ProjectDetail(DetailView):
    model = Project
    template_name = 'experiments/project_detail.html'
    pk_url_kwarg = 'project_id'


    def get_context_data(self, **kwargs):
        context = super(ProjectDetail, self).get_context_data()
        project = get_object_or_404(Project,pk=self.kwargs["project_id"])
        context["project"] = project
        context["collaborator_form"] = CollaboratorForm()
        return context

class InviteCollaborator(FormView):
    form_class = CollaboratorForm
    template_name = 'experiments/project_detail.html'

    def post(self, request, *args, **kwargs):
            collaborator_form = CollaboratorForm(data=request.POST)
            project_id = request.POST['project_id']
            current_project = Project.objects.get(id=project_id)
            datas={}
            if collaborator_form.is_valid():
                cleaned_data = collaborator_form.cleaned_data
                email_address = cleaned_data.get('email_address')
                user = User.objects.get(pk=request.user.id)
                invitation_link = "http://exp.innovationhackinglab.com/projects/"+ str(current_project.id) + "/join/" + current_project.invitation_key
                datas['user_name'] = user.first_name + ' ' + user.last_name
                datas['email_from'] = user.email
                datas['email_to'] = email_address
                datas['invitation_link'] = invitation_link
                collaborator_form.sendEmail(datas)
                data = simplejson.dumps("Success")
                return HttpResponse(data, content_type='application/json')
            else:
                return super(InviteCollaborator, self).form_invalid(collaborator_form)

invite_collaborator.js

function invite(project_id) {
    $('#collaborator-form').submit(function(e) {
        e.preventDefault();
        $.ajax({
            data: $(this).serialize()+'&'+$.param({ 'project_id': project_id }),
            type: $(this).attr('method'),
            url: $(this).attr('action'),
            });
    $('#collaboratorModal').modal('toggle');
    $('#collaboratorModal').on('hidden.bs.modal', function () {
        $(this).find("input,textarea,select").val('').end();
            });
        });
    };

I've read about using success: & error: on the js file but don't know how to use it without the appropriate "return" in the view

2
Where exactly do you set project variable in context?Mateusz Knapczyk
Just edited my question, added ProjectDetail in views.py. I set the project in that view context.Gonzalo Machado

2 Answers

0
votes

You need to have two ajax methods, one to get the form (as raw html) and one to post the form. You will have a corresponding get and post method in your view too.

get function of your view class:

def get(self, request, *args, **kwargs):

  form = CollaboratorForm()  
  return render(request,'template.html',{'form':form})

def post(self, request, *args, **kwargs):

  form = CollaboratorForm(request.POST)
  if form.is_valid():
     //save form
     //return whatever you want to show on successful form submission
  else:
     //return bound form as html with errors  
     return render(request,'template.html',{'form':form}) 

js functions

have two seperate ajax function one for get (showing form) one for post(submitting form)

0
votes

If you want to use templates on server's side, with FormView and ajax, I would suggest splitting templates into two parts - wrapper and form, load only wrapper via TemplateView, then fetch form with ajax. That allows you to send form with ajax and put responses (like form with errors) in wrapper.

  1. Change your HTML template - take modal body's to another file, ex.:

project_detail.html

<script src="{% static '/experiments/js/invite_collaborator.js' %}"></script>

<div class="bootstrap-modal modal fade in" id="collaboratorModal" style="display: none;">
    <div class="modal-body" id="collaboratorModalContent">        
    </div>
</div>

project_detail_content.html

<form  action="{% url 'experiments:invite-collaborator' project_id=project.id %}" method="post" id=collaborator-form >
          {% csrf_token %}

    <div class="form-group">
    {% if collaborator_form.errors %}
        <ol>
        {% for error in collaborator_form.errors %}
            <li><strong>{{ error|escape }}</strong></li>
        {% endfor %}
        </ol>
    {% endif %}

    <label class="control-label">Invite someone by email</label>
    <div class="input-group mt10">
        {{ collaborator_form }}
    <span class="input-group-btn">
    <input name="collaborator-commit" onClick="invite({{project.id}});" class="btn btn-primary" data-disable-with="Send Invitation" id="invite-button" type="submit">
    </span>
    </div>
    </div>
</form>
  1. FormView should handle GET and POST - first one to get the form in project_detail_content.html into modal, second for sending email. Fortunately, FormView can do all that for us! (I don't know from where you get that project variable though)

View.py

class InviteCollaborator(FormView):
    form_class = CollaboratorForm
    template_name = 'experiments/project_detail_content.html'

    def form_valid(self, form):
        # This method is called when valid form data has been POSTed.
        # It should return an HttpResponse.
        project_id = self.request.POST['project_id']
        current_project = Project.objects.get(id=project_id)
        datas={}
        cleaned_data = form.cleaned_data
        email_address = cleaned_data.get('email_address')
        user = User.objects.get(pk=request.user.id)
        invitation_link = "http://exp.innovationhackinglab.com/projects/"+ str(current_project.id) + "/join/" + current_project.invitation_key
        datas['user_name'] = user.first_name + ' ' + user.last_name
        datas['email_from'] = user.email
        datas['email_to'] = email_address
        datas['invitation_link'] = invitation_link
        form.sendEmail(datas)
        data = simplejson.dumps("Success")
        return HttpResponse(data, content_type='application/json')

Note few things - we use FormView, so for GET request it will return content of project_detail_content.html with CollaboratorForm, and on POST, same template with form and errors if form is invalid, or JSON with Success message otherwise.

  1. What happened to project_detail.html? We will use TemplateView to create thw wrapper:

Url.py

urlpatterns = [
    url(r'^invite_collaborator$', TemplateView.as_view(template_name="project_detail.html")),
    url(r'^(?P<project_id>[0-9]+)/invite_collaborator/form$', views.InviteCollaborator.as_view(), name='invite-collaborator'),
]
  1. Finally, JS

invite_collaborator.js

// In JS you need to make sure you fetch form from /project_id/invite_collaborator/form each time you show modal
$(document).ready(function(e) {
    $('#collaboratorModalContent').load('invite_collaborator');
});

// Then, on submit we simply send data and handle response with success and error.
// With our current View, invalid form will generate successful response with form and error, so we need to check 

function invite(project_id) {
    $('#collaborator-form').submit(function(e) {
        e.preventDefault();
        $.ajax({
            type: $(this).attr('method'),
            url: $(this).attr('action'),
            data: $(this).serialize()+'&'+$.param({ 'project_id': project_id }),
            success: function ( response, status, xhr, dataType ) {
                if( dataType === 'json' ){
                    //Make sure response is 'Success' and close modal
                    $('#collaboratorModal').modal('toggle');
                    $('#collaboratorModal').on('hidden.bs.modal', function () {
                        $(this).find("input,textarea,select").val('').end();
                            });
                        });
                    };
                }
                else {
                    // It's not JSON, it must be HttpResposne with forms and errors, so it goes into modal's body
                    $('#collaboratorModalContent').html(response)
                }

            }
        });

I still don't know where and how you get/set you project variable, so maybe TemplateView is bad choice...