Opened 6 years ago

Closed 4 years ago

#9150 closed (fixed)

Generic views should accept arguements for form_class

Reported by: erikcw Owned by: toabctl
Component: Generic views Version: master
Severity: Keywords: generic views, form_class, arguments
Cc: darkpixel Triage Stage: Design decision needed
Has patch: yes Needs documentation: no
Needs tests: yes Patch needs improvement: no
Easy pickings: UI/UX:

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 toabctl 6 years ago.
Patch:add form_params={} to pass content to forms within generic views
generic-views.txt.diff (980 bytes) - added by toabctl 6 years ago.
Update Documentation for generic views and add form_params as parameter

Download all attachments as: .zip

Change History (17)

comment:1 Changed 6 years ago by anonymous

  • milestone post-1.0 deleted

Milestone post-1.0 deleted

comment:2 Changed 6 years ago by jacob

  • Needs documentation unset
  • Needs tests unset
  • Patch needs improvement unset
  • Triage Stage changed from Unreviewed to Design decision needed

comment:3 in reply to: ↑ description Changed 6 years ago by toabctl

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 Changed 6 years ago by toabctl

  • Owner changed from nobody to toabctl
  • Status changed from new to assigned

comment:5 Changed 6 years ago by toabctl

  • Has patch set
  • milestone set to 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 follow-up: Changed 6 years ago by kmtracey

  • milestone 1.1 deleted
  • 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

Changed 6 years ago by toabctl

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

comment:7 in reply to: ↑ 6 Changed 6 years ago by toabctl

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

Changed 6 years ago by toabctl

Update Documentation for generic views and add form_params as parameter

comment:8 Changed 6 years ago by anonymous

  • milestone set to 1.2

comment:9 Changed 6 years ago by anonymous

  • milestone 1.2 deleted
  • Needs documentation unset
  • Patch needs improvement unset

comment:10 Changed 6 years ago by toabctl

  • milestone set to 1.2
  • Version changed from 1.0 to SVN

comment:11 Changed 5 years ago by darkpixel

  • Cc darkpixel added

comment:12 Changed 5 years ago by lukeplant

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 Changed 5 years ago by ubernostrum

  • milestone 1.2 deleted

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

comment:14 Changed 5 years ago by sorki

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 Changed 4 years ago by russellm

  • Resolution set to fixed
  • Status changed from assigned to closed

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