13

I need to have a form that allows the creation or addition of sessions on a planning

Model

class Session(models.Model):
    tutor = models.ForeignKey(User)
    start_time = models.DateTimeField()
    end_time = models.DateTimeField()

Form

class SessionForm(forms.ModelForm):
    class Meta:
        model = Session
        exclude = ['tutor']

View to render the form

def editor(request):
    if request.method == 'GET':
        if request.GET['id'] != '0':
            # The user has selected a session
            session = Session.objects.get(id=request.GET['id'])
            form = SessionForm(instance=session)
        else:
            # The user wants to add a new session
            form = SessionForm()
        return render_to_response('planner/editor.html',
            {'form': form,}, context_instance=RequestContext(request),)

Template editor.html

<form action="/planner/post" method="post">{% csrf_token %}
{{ form.as_p }}
</form>

View to post the values

def post(request):
    if request.method == 'POST':
        form = SessionForm(request.POST)
        if form.is_valid():
            form.instance.tutor = request.user
            form.save()
            obj = {'posted': True}
            return HttpResponse(json.dumps(obj), mimetype='application/json')
        else:
            return render_to_response('planner/editor.html',
                form, context_instance=RequestContext(request),)

Problem

Sessions are always created (never updated)

Questions

  • In my view post how do I know that the session must be updated and not created ?
  • Is there a way to simplify this code ?
Pierre de LESPINAY
  • 44,700
  • 57
  • 210
  • 307
  • Solution: pass the instance to form, for now it has nothing to update. – DrTyrsa Apr 18 '12 at 09:02
  • This indeed seems to be the problem, but how can I get the actual instance ? Do I have to add the id to the template manually ? – Pierre de LESPINAY Apr 18 '12 at 09:13
  • And how do you want your app know what to do: create new session or edit existing one? I think the standard solution is to use different URL's for these. – DrTyrsa Apr 18 '12 at 09:16
  • Different URLs for the editor or the post ? I know that my view can't know if the session have to be created or not. Now I'm wondering about the best/simple way to get the actual instance. – Pierre de LESPINAY Apr 18 '12 at 09:21

4 Answers4

25

If you want to update a session, you need to provide the instance when you bind the form.

If the form is valid, you can then save with commit=False, and update the tutor.

form = SessionForm(instance=instance, data=request.POST)
if form.is_valid():
    instance = form.save(commit=False)
    instance.tutor = request.user
    instance.save()
Alasdair
  • 298,606
  • 55
  • 578
  • 516
  • How can I have the instance if I don't know the id of the posted session ? Do I have to add it manually to the html form ? – Pierre de LESPINAY Apr 18 '12 at 09:04
  • In Django it is common to include the id of the object in your urls (see, for example, [part 3 of the tutorial](https://docs.djangoproject.com/en/dev/intro/tutorial03/#design-your-urls)). If you don't do that, then you can include the id as a `GET` or `POST` parameter. You would have to add it to the form manually. As an aside, you might need to make sure that the user has permission to change the session they are trying to edit. – Alasdair Apr 18 '12 at 09:21
  • So you think my template form should be like `
    ` ?
    – Pierre de LESPINAY Apr 18 '12 at 09:24
  • 2
    That's the idea, although it's good practice to [reverse your urls](https://docs.djangoproject.com/en/dev/topics/http/urls/#naming-url-patterns), then you can have `action="{% url edit_session session.pk %}"` – Alasdair Apr 18 '12 at 09:26
  • Ok I see, thank you. Using the URL dispatcher to put params is not yet natural for me – Pierre de LESPINAY Apr 18 '12 at 09:43
1
from django.shortcuts import render_to_response, get_object_or_404
from django.http import HttpResponseRedirect, Http404
from django.template import RequestContext
from application.models import Session
from application.forms import SessionForm

def allInOneView(request):
    session_id = request.POST.get('session_id')

    if session_id:
        session = get_object_or_404(Session, pk=session_id)
    else:
        session = None

    """
    A subclass of ModelForm can accept an existing model instance 
    as the keyword argument instance; 
    if this is supplied, save() will update that instance. 
    If it's not supplied, save() will create a new instance of the specified model.
    """
    form = SessionForm(instance=session)

    if request.method == 'POST':
        form = SessionForm(request.POST, instance=session)
        if form.is_valid():
            form.save()
            return HttpResponseRedirect(request.path)

    return render_to_response('planner/editor.html', {
        'form': form
    }, context_instance=RequestContext(request))
niko.makela
  • 537
  • 3
  • 14
  • Your answer seems the most interesting for me (not tested yet). is it ok to put `if session_id` ? isn't it always going to evaluate to `False` ? – Pierre de LESPINAY Apr 19 '12 at 08:09
  • request.POST.get() method returns a value for the given key. If the key is not available, then it returns a default value which is None. If you have any value set in a variable, it won't be evaluated as False. – niko.makela Apr 19 '12 at 12:39
  • Actually.. I guess request.POST.get() could be changed to request.REQUEST.get() so it would deal both, the GET and POST requests; http://stackoverflow.com/a/4162731/784642 – niko.makela Apr 19 '12 at 20:03
1

What I usually do now (following the advices mentioned here) is using only one view passing the optional session_id (no session_id for creation) to the URL dispatcher.

<form action="{% url session_edit session_id=session_id|default_if_none:"" %}"
    method="post">{% csrf_token %}
{{ form.as_p }}
</form>
url('^planner/edit$', session_edit, name='session_edit'),
url('^planner/edit/(?P<session_id>\d+)$', session_edit, name='session_edit'),

I find that regrouping all 4 cases

  • Get creation form
  • Get update form
  • Post creation form
  • Post update form

into one view is much more maintainable.

Pierre de LESPINAY
  • 44,700
  • 57
  • 210
  • 307
0

Do it all in the one view. Something like:

def session_manager(request):

    session = None
    try:
        session = Session.objects.get(id=request.POST['id'])
    except Session.DoesNotExist:
        pass

    if request.method == "POST":
       kwargs = {
            data = request.POST
       }
       if session:
            # Update
            kwargs['instance'] session 
       form = SessionForm(**kwargs)
       if form.is_valid():
            ...
    else:
        form = SessionForm(instance=session)
Timmy O'Mahony
  • 53,000
  • 18
  • 155
  • 177
  • The thing is `id` is never posted in the post view. My editor template is like `{{ form.as_p }}`. Should I had the `id` to it manually ? – Pierre de LESPINAY Apr 18 '12 at 08:58
  • 1
    You can try and do something like `Session.object.get(tutor=request.user, start_time=request.POST['start_time'], end_time=request.POST['end_time'])`, but it would be easier to include the `Session` id in the form as a hidden field and uses that to retrieve the `Session` object. – Timmy O'Mahony Apr 18 '12 at 09:02
  • I agree more on the second solution, but is it normal to add it manually ? – Pierre de LESPINAY Apr 18 '12 at 09:11