Let's say we have an app called Closet and it has some models:
# closet.models.py
class Outfit(models.Model):
shirt = models.ForeignKey(Shirt)
pants = models.ForeignKey(Trouser)
class Shirt(models.Model):
desc = models.TextField()
class Trouser(models.Model):
desc = models.TextField()
class Footwear(models.Model):
desc = models.TextField
Using generic detail view, it's easy to make the URL conf for details on each of those:
#urls.py
urlpatterns = patterns('',
url(r'^closet/outfit/(?P<pk>\d+)$', DetailView(model=Outfit), name='outfit_detail'),
url(r'^closet/shirt/(?P<pk>\d+)$', DetailView(model=Shirt), name='shirt_detail'),
url(r'^closet/trouser/(?P<pk>\d+)$', DetailView(model=Trouser), name='trouser_detail'),
url(r'^closet/footwear/(?P<pk>\d+)$', DetailView(model=Footwear), name='footwear_detail'),
)
What I'd like to do next is define the views that will create a new object of each type. I would like to do this with an extended version of CreateView
which will be able to handle data on pre-populated fields.
Specifically, I want the following behavior:
- If I visit
/closet/outfit/new
I want to get a standardModelForm
for theOutfit
model with everything blank and everything editable. - If I visit
/closet/outfit/new/?shirt=1
I want to see all the fields I saw in case 1) but I want the shirt field to be pre-populated with the shirt with pk=1. Additionally, I want the shirt field to be displayed as un-editable. If the form is submitted and is deemed to be invalid, when the form is redisplayed I want the shirt field to continue to be un-editable. - If I visit
/closet/outfit/new/?shirt=1&trouser=2
I want to see all the fields I saw in case 1) but now both the shirt and trouser fields should be preopoulated and uneditable. (I.e. only thefootwear
field should be editable.)
In general, is this possible? I.e. can the querystring modify the structure of the displayed form in this way? I want to accomplish this in the DRYest way possible. My gut tells me this should be doable with class based views and perhaps would involve model_form_factory
but I can't get the logic straight in my mind. In particular, I wasn't sure whether it was possible to have the class-based-view access the request.REQUEST
(i.e. the request.POST
or request.GET
parameters) at the time that the ModelForm
is being constructed.
Perhaps its possible only if I use different querystring keywords for the locked fields. I.e. perhaps the URL's need to be: /closet/outfit/new/?lock_shirt=1
and /closet/outfit/new?lock_shirt=1&lock_trouser=2
. Perhaps if its done that way the POST
handler would be handed both a list of locked fields (for the purposes of form display in the browser) along with a regular list of all the model fields for the purpose of actually creating the object.
Why do I want this: In the template for the footwear_detail
I would want to be able to make a tag like
<a href="{% url outfit_new %}?footwear={{object.pk}}>Click to create a new outfit with this footwear!</a>
In general, it would be really useful to be able to make links to forms whose "structure" (not just values) changes depending on the querystring passed.
Responding to the great suggestion from Berislav Lopac:
So I went ahead and did:
class CreateViewWithPredefined(CreateView):
def get_initial(self):
return self.request.GET
This gets me 90% of what I need. But let me flesh out the situation a bit more. Say I add two fields to the Outfit model: headgear = models.ManyToManyField('headgear')
and awesomeness_rating = models.FloatField()
.
Two problems:
- If I visit
/closet/outfit/new/?awesomeness_rating=10
then my form pre-fills with[u'10']
instead of just filling with10
. Is there a filter I should use in my template or a bit of processing I can add to my view to make the formatting more appropriate? - If I want to pre-specify a few pieces of headwear, what is the right format to pass what feels like a python list in through a query string? I.e. should I do
/closet/outfit/new/?headgear=1,2,3
? If so, will Django correctly figure out that I'd like to pre-select the 3 pieces of headgear with those ID's?
Continuing to work on this...
class CreateViewWithPredefined(CreateView):
def get_initial(self):
initial = super(CreateView, self).get_initial()
for k, v in self.request.GET.iterlists():
if len(v) > 1:
initial.update({ k : v })
else:
initial.update({ k : v[0] })
return initial
This seems to kill 2 birds with one stone: numerical data gets coerced from unicode to numerical and it flattens lists when possible (as intended). Need to check if this works on multi-valued fields.