hey Lachlan,
So this had driven me a bit crazy some years ago ..
On Wed, May 28, 2014 at 8:42 AM, Lachlan Musicman <datakid@gmail.com> wrote:
> Hola,
>
> I am trying to get two different "extras" to work.
>
> I have a form with an inline_formset, which I would like to be able to
> add multiple formsets of on the fly.
>
> Each of those formsets has another FK to a model with a very large
> dataset, so I would also like to implement some sort of
> autocomplete/ajax/select2/typeahead solution to prevent an unusably
> large select
>
I had two additional extras :):
i> The FK could also be an m2m that should be handled by select2.
ii> There should also be an Add + button next to the select2 to add a
new instance, which opens up a pop-up to add a new item, like it does
in the Django admin.
And then I needed this on a whole bunch of models / forms, so I needed
it abstracted / generalized in some way. Unfortunately, the solution I
came up with involves some ugly hacks, so I'm slightly embarassed by
it, haha - its also for a much older version of Django, etc. (1.3,
iirc).
However, I still think this is a really kick-ass and useful thing to
be able to do simply - I'd be happy to try and catch up with you
off-list (which we've been meaning to do anyways, ha!) and see if we
can have some fun rolling a little django app for this or so.
> Here is my basic set up.
>
> models.py
> ----------------
> class PartNumber(models.Model):
> name = models.CharField("Description", max_length=100)
> supplier_part_number = models.CharField(max_length=30,
> unique=True, blank=True, null=True)
>
> class PurchaseOrder(models.Model):
> po_number = models.CharField('PO number', max_length=10, unique=True)
> ordered_date = models.DateField(default=today)
>
> class PurchaseOrderPart(models.Model):
> part_number = models.ForeignKey(PartNumber, related_name='purchases')
> po_number = models.ForeignKey(PurchaseOrder, related_name='partslist')
> delivery_date = models.DateField(null=True, blank=True)
> qty_ordered = models.IntegerField('Quantity
> ordered',validators=[MinValueValidator(1)])
> cost = models.DecimalField('Unit Cost',
> max_digits=10,decimal_places=2,blank=True,null=True)
>
>
> forms.py
> -------------
>
> class PurchaseOrderPartForm(forms.ModelForm):
> class Meta:
> fields = ('part_numbers', 'delivery_date', 'qty_ordered', 'cost')
> model = PurchaseOrderPart
> widgets={
> 'part_numbers': forms.Select(attrs={'class':'form-control'}),
> 'delivery_date': CalendarWidget(attrs={'class':'input-append
> form-control'}),
> 'qty_ordered': forms.NumberInput(attrs={'class':'form-control'}),
> 'cost': forms.NumberInput(attrs={'class':'form-control'}),
> }
>
> POPartFormset = inlineformset_factory(PurchaseOrder,
> PurchaseOrderPart, form=PurchaseOrderPartForm, extra=1,
> can_delete=True)
>
>
> I have successfully implemented jquery.formset.js (
> https://github.com/elo80ka/django-dynamic-formset ) to create "add"
> and "remove" buttons - this allows for a variable number of
> PurchaseOrderParts on a PurchaseOrder.
>
>
> When I then implemented django-select2 I was mostly successful, except
> for one problem - the way that django-select2 implements field is by
> adding some js on render.
>
> Since the jquery.formset.js was creating forms on the fly, any
> formsets added via it's add button did not have the required
> django-select2 container, and hence had no widget at all.
>
> jquery.formset.js has an "added" function that allows for something
> like this, but then I would need to glue it all together and it
> doesn't seem clean enough.
>
So, I used a similar approach - jquery.formset.js and select2. What I
did was I created a little jquery plugin / wrapper around select2 to
be able to instantiate it with a simple call to like
$('#select_field_id_foo').mySelect2();. I then also created a custom
widget for the autocompletes, which would ensure their custom options
were in the html to be read by the plugin.
So, this is the custom widget class I made:
http://code.camputer.org/itf/annotate/head%3A/itf/app/forms.py#L42
This is its template:
http://code.camputer.org/itf/annotate/head%3A/itf/templates/formwidgets/select2.html
Then I can simply instantiate the widget in my form definition like so:
group = forms.ModelChoiceField(TheatreGroup.objects.all(),
widget=AutocompleteAddWidget(model_class=TheatreGroup))
See for eg: http://code.camputer.org/itf/annotate/head%3A/itf/itfprofiles/forms.py#L129
So far so good. Where the ugly hackery lies is the javascript - as you
mention, to work around the problem of instantiating when adding a new
formset and other problems when using the Django Admin JS for the
pop-up forms, I landed up hacking in the jquery.formset.js code
itself, which I'm not proud of. I think this can probably be
accomplished cleaner using the 'added' callback or so, as you mention
(and make your single point of entry / instantiation a custom jquery
plugin / wrapper around select2).
So anyways, first, this is the custom jquery plugin / wrapper I made
for select2:
http://code.camputer.org/itf/annotate/head%3A/itf/static/js/itfSelect2.js
That reads some params output in the HTML for the select, and
instantiates select2 appropriately. Was a bit of a yak-shave to get
the initial value to display correctly, etc. and this was a while ago,
so I don't remember details of why some of those things are that way,
but I do know this code still works, and as I said, I'd be excited to
collaborate with you on releasing something 'clean' for this use-case.
So, anyways, to complete the story .. this is the ugliness where I
modified the jquery.formset.js code, which is obviously a bit nasty to
be doing :/ - http://code.camputer.org/itf/annotate/head%3A/itf/static/js/jquery.formset.js#L90
There's additional JS bits and ugly hacks to get the "add" button
working, but I'll save you the gory details since you don't seem to
need that :-)
Additionally, this is the view I came up with to handle outputting the
autocomplete data on the back-end - it accepts a ContentType id for
the model it needs to autocomplete for, and returns JSON for the model
appropriately. It depends on some methods being defined on the model:
http://code.camputer.org/itf/annotate/head%3A/itf/app/views.py#L19
So, this was a while ago when I honestly had little idea about the
internals of django forms, etc. so its probably not the best way,
though I did manage to get everything to work the way I wanted in the
end.
As I said, I'd love to collaborate with you to wrap something neatly
for this use-case - please feel free to hit me up on chat or anything,
and hopefully we can post back on the list when we have something
working - unless, of course, there is already a straightforward way to
do this :)
> I had previously looked at typeahead, django-ajax-selects and
> django-autocomplete-light but struggled to implement them. I will
> likely go back to try again.
>
Nooooooooooo hahaha - I personally like select2, its nice -- it does
have some problems on mobile / touch interfaces which I'm not sure are
resolved, so if you need that, it maybe worth looking at other
options. I've had to use typeahead on another project, and have been
using django-ajax-selects in the admin for small things, but overall I
really like the customizability of select2 - it really feels like the
cleanest of them and I've really enjoyed using it. Of course, this is
also based on my impressions from at least a year ago, so things may
have changed.
> Surely I'm not the first person that's had this need - can anyone
> offer tips on a good or better solution?
>
Haha, I thought I was some sort of crazy person for needing this a few
years ago - glad someone else has bumped into it - I think a
straightforward clean solution to be able to do this would be really,
really nice to have - it does seem like such an obvious thing one
would want. I haven't revisited this in some time, but I do remember
being completely obsessed with this for a month or so a few years ago.
Would love to go back to it and use some of the experience gained then
to make something actually semi-elegant now - do let me know.
Talk soon!
-Sanjay
> cheers
> L.
>
>
>
> --
> The idea is that a beautiful image is frameable. Everything you need
> to see is there: It's everything you want, and it's very pleasing
> because there's no extra information that you don't get to see.
> Everything's in a nice package for you. But sublime art is
> unframeable: It's an image or idea that implies that there's a bigger
> image or idea that you can't see: You're only getting to look at a
> fraction of it, and in that way it's both beautiful and scary, because
> it's reminding you that there's more that you don't have access to.
> It's now sort of left the piece itself and it's become your own
> invention, so it's personal as well as being scary as well as being
> beautiful, which is what I really like about art like that.
> -----------------------------------------------------------------------------------------------------------
p.s. I really like the quotes you attach, thanks, hah :-)
--
You received this message because you are subscribed to the Google Groups "Django users" group.
To unsubscribe from this group and stop receiving emails from it, send an email to django-users+unsubscribe@googlegroups.com.
To post to this group, send email to django-users@googlegroups.com.
Visit this group at http://groups.google.com/group/django-users.
To view this discussion on the web visit https://groups.google.com/d/msgid/django-users/CAG3W7ZGeKQwBfPzySwz5D1%3DNd-m%3D91%3DzPNOsmHpcxqpaquY%3D3w%40mail.gmail.com.
For more options, visit https://groups.google.com/d/optout.
Subscribe to:
Post Comments (Atom)
No comments:
Post a Comment