Opened 16 years ago

Closed 13 years ago

#9150 closed (fixed)

Generic views should accept arguements for form_class

Reported by: ErikW Owned by: Thomas Bechtold
Component: Generic views Version: dev
Severity: Keywords: generic views, form_class, arguments
Cc: Aaron C. de Bruyn Triage Stage: Design decision needed
Has patch: yes Needs documentation: no
Needs tests: yes Patch needs improvement: no
Easy pickings: no UI/UX: no

Description

The form_class property for generic views such a django.views.generic.create_update.* should accept either a ModelForm class or a ModelForm object. This would allow generic "wrapper views" to easily inject run-time data into the form instance.

This will be useful for sites which use contrib.auth to create "members only" area. Such sites would be able to use generic views while restricting the view's queryset to the currently logged in user. It would also make it easier for the create_object generic view to set the "user ForeignKey" to the currently logged in user behind the scenes. Right now, request.POST has to be manipulated in a wrapper view to hide this field from the end-user.

If form_class would accept ModelForm(initial={'user': request.user,}) instead of just ModelForm, this problem would be eased. Alternatively, maybe generic views could accept another property containing a dictionary of desired arguments to feed to the ModelForm object.

I think this or a similar enhancement would greatly expand the situations where generics can be used.

Attachments (2)

create_update.py.diff (2.5 KB ) - added by Thomas Bechtold 15 years ago.
Patch:add form_params={} to pass content to forms within generic views
generic-views.txt.diff (980 bytes ) - added by Thomas Bechtold 15 years ago.
Update Documentation for generic views and add form_params as parameter

Download all attachments as: .zip

Change History (17)

comment:1 by (none), 15 years ago

milestone: post-1.0

Milestone post-1.0 deleted

comment:2 by Jacob, 15 years ago

Triage Stage: UnreviewedDesign decision needed

in reply to:  description comment:3 by Thomas Bechtold, 15 years ago

Replying to erikcw:

The form_class property for generic views such a django.views.generic.create_update.* should accept either a ModelForm class or a ModelForm object. This would allow generic "wrapper views" to easily inject run-time data into the form instance.

This will be useful for sites which use contrib.auth to create "members only" area. Such sites would be able to use generic views while restricting the view's queryset to the currently logged in user. It would also make it easier for the create_object generic view to set the "user ForeignKey" to the currently logged in user behind the scenes. Right now, request.POST has to be manipulated in a wrapper view to hide this field from the end-user.

If form_class would accept ModelForm(initial={'user': request.user,}) instead of just ModelForm, this problem would be eased. Alternatively, maybe generic views could accept another property containing a dictionary of desired arguments to feed to the ModelForm object.

I think this or a similar enhancement would greatly expand the situations where generics can be used.

Here is an example how it should be:

models.py:

from django.db import models

class Project(models.Model):
    name = models.CharField(max_length=200)
    auth_group = models.ForeignKey('auth.Group')
    def __unicode__(self):
        return self.name

forms.py:

from models import Project

class ProjectForm(ModelForm):
    def __init__(self, request, *args, **kwargs):
        super(ProjectForm, self).__init__(*args, **kwargs)
        #set possible groups to the users groups
        self.fields['auth_group'].queryset = request.user.groups.all()
    class Meta:
        model = Project

views.py:

from forms import ProjectForm
from django.views.generic import create_update
def project_new(request):
    response = create_update.create_object(
        request,
        model = Project,
        form_class = ProjectForm(request),             # actually, only this is possible: form_class = ProjectForm 
                                                       # but the ProjectForm requires one arguement (see forms.py)
        template_name = 'project_form.html',
    )
    return response

comment:4 by Thomas Bechtold, 15 years ago

Owner: changed from nobody to Thomas Bechtold
Status: newassigned

comment:5 by Thomas Bechtold, 15 years ago

Has patch: set
milestone: 1.1

Attachment attachment:create_update.py.diff is a patch for this problem.

how the patch works

The Patch adds a new parameter for the create_object() method and update_object() method. the new parameter is called form_params (type is dict). it's now possible to pass parameters to the constructor of the form.

Example

models.py

class Project(models.Model):
    """ 
    A Model which describe a Project 
    """
    name = models.CharField(max_length=200)
        auth_group = models.ForeignKey('auth.Group')
    def __unicode__(self):
        return self.name

forms.py

class ProjectForm(ModelForm):
    """
    Form for a project
    """
    def __init__(self, groups, *args, **kwargs):
        super(ProjectForm, self).__init__(*args, **kwargs)
        #set possible groups to the users groups
        self.fields['auth_group'].queryset = groups
    class Meta:
        model = Project

ProjectForm needs as parameter the variable groups. This is normally request.user.groups.all().

views.py

def project_new(request):
    """ 
    A Form to create a new Project 
    """
    response = create_update.create_object(
        request,
        form_class = ProjectForm,
        form_params = {'groups':request.user.groups.all(),},
        template_name = 'project_form.html',
    )
    return response

def project_update(request, project_id):
    """ 
    A Form to Update a Project
    """
    response = create_update.update_object(
        request,
        form_class = ProjectForm,
        form_params = {'groups':request.user.groups.all(),},
        template_name = 'project_form.html',
        object_id = int(project_id),
        post_save_redirect = reverse('project-list')
    )
    #return response

in both methods, there is the var form_params passed.

project_form.html

{% if form %}
<h1>{% trans "New Project" %}</h1>
	<form action="." method="POST">
    	<table>
        	{{ form.as_table }}
    	</table>
    	<p><input type="submit" value="Submit"></p>
	</form>
{% else %}

{% endif %}	

comment:6 by Karen Tracey, 15 years ago

milestone: 1.1
Needs documentation: set
Needs tests: set
Patch needs improvement: set

Only absolutely critical bugs go into 1.1 at this point. This is a feature/new function -- and it still in design decision needed, not accepted, stage -- so 1.1 is not an appropriate milestone.

Also, the patch should be generated from the root of the source tree, and is currently lacking both documentation and tests. Please read:

http://docs.djangoproject.com/en/dev/internals/contributing/#patch-style

by Thomas Bechtold, 15 years ago

Attachment: create_update.py.diff added

Patch:add form_params={} to pass content to forms within generic views

in reply to:  6 comment:7 by Thomas Bechtold, 15 years ago

Replying to kmtracey:

Only absolutely critical bugs go into 1.1 at this point. This is a feature/new function -- and it still in design decision needed, not accepted, stage -- so 1.1 is not an appropriate milestone.

ok. i understand this point. but the ticket is 10 month old. when will be a design decision?

Also, the patch should be generated from the root of the source tree, and is currently lacking both documentation and tests. Please read:

http://docs.djangoproject.com/en/dev/internals/contributing/#patch-style

the new patch fix this problem. i also added some documentation

by Thomas Bechtold, 15 years ago

Attachment: generic-views.txt.diff added

Update Documentation for generic views and add form_params as parameter

comment:8 by anonymous, 15 years ago

milestone: 1.2

comment:9 by anonymous, 15 years ago

milestone: 1.2
Needs documentation: unset
Patch needs improvement: unset

comment:10 by Thomas Bechtold, 15 years ago

milestone: 1.2
Version: 1.0SVN

comment:11 by Aaron C. de Bruyn, 14 years ago

Cc: Aaron C. de Bruyn added

comment:12 by Luke Plant, 14 years ago

The alternative, which requires no changes to Django, is to use a wrapper function with a dynamically created ModelForm that includes the required behaviour, as illustrated on #12392.

comment:13 by James Bennett, 14 years ago

milestone: 1.2

1.2 is feature-frozen, moving this feature request off the milestone.

comment:14 by sorki, 14 years ago

You can set initial data this way

from django.views.generic import create_update

def custom_create(request, *args, **kwargs):
    ''' update __init__ of the form class to use current user as initial '''
    form_class = kwargs['form_class']

    class custom_class(form_class):
        def __init__(self, *args, **kwargs):
            kwargs['initial'] = {'user': request.user.id}
            return super(custom_class, self).__init__(*args, **kwargs)

    kwargs['form_class'] = custom_class 
    return create_update.create_object(request, *args, **kwargs)

and use it the same way you use create_object generic view (except passing model to it).

(WORKSFORME)

comment:15 by Russell Keith-Magee, 13 years ago

Resolution: fixed
Status: assignedclosed

Function-based generic views were deprecated by the introduction of class-based views in [14254]. Class-based views should solve this problem.

Note: See TracTickets for help on using tickets.
Back to Top