Re: django: creating tag groups

125 views
Skip to first unread message

Amirouche

unread,
Jan 14, 2013, 8:56:51 PM1/14/13
to django...@googlegroups.com
Why are you creating two groups of tags ? Why not use a specific widgets like a tree widget ?

On Monday, January 14, 2013 12:02:59 PM UTC+1, Sammael wrote:
The models

    class TagGroup(models.Model):
        name = models.SlugField(max_length=50, unique=True, db_index=True)

    class Tag(models.Model):
        name = models.SlugField(max_length=50, unique=True, db_index=True)
        group = models.ForeignKey(TagGroup)
        def __unicode__(self):
            return self.name

    class Album(models.Model):
        name = models.CharField(max_length=64, blank=True)
        tags = models.ManyToManyField(Tag, blank=True)

The idea is to have some groups of tags, for example, season (with the following tags: winter, summer, etc.) and place (with tags like Europe, America) for each album.

The problem is I can't find a way to have a separate form for each group of tags in Django Admin.

I've been reading docs for many hours trying to find the solution and here is where I come.


First approach

First of all I decided to create a separate form for tags:

    class AlbumAdminForm(forms.ModelForm):
        class Meta:
            model = Album
        tags = forms.ModelMultipleChoiceField(queryset=Tag.objects.all(), required=False, widget=FilteredSelectMultiple('tags', False ))

    class AlbumAdmin(admin.ModelAdmin):
        form = AlbumAdminForm

It works good. Trying to add another form like this:

    class AlbumAdminForm(forms.ModelForm):
        class Meta:
            model = Album
        tags = forms.ModelMultipleChoiceField(queryset=Tag.objects.filter(group = 1), required=False, widget=FilteredSelectMultiple('tags', False ))
        tags_2 = forms.ModelMultipleChoiceField(queryset=Tag.objects.filter(group = 2), required=False, widget=FilteredSelectMultiple('tags', False ))

In this case I see two forms in django admin with tags I can select, but can't save the contents of the second form because of it's property name 'tags_2' - django relies on this name. Unfortunately, I didn't find any way to make this form use Album.tags to save it's values.

Second approach

As far as I couldn't find the right way with AlbumAdminForm itself I decided to change html code it generates. First form looks like this:

    <select multiple="multiple" class="selectfilter" name="tags" id="id_tags">
    </select><script type="text/javascript">
        addEvent(window, "load", function(e) {SelectFilter.init("id_tags", "tags", 0, "/static/admin/"); });
    </script>

I was trying to make the second one as similar as possible, but the only thing I could change is it's id:


    class AlbumAdminForm(forms.ModelForm):
        class Meta:
            model = Album
        tags = forms.ModelMultipleChoiceField(queryset=Tag.objects.filter(group = 1), required=False, widget=FilteredSelectMultiple('tags', False ))
        tags_2 = forms.ModelMultipleChoiceField(queryset=Tag.objects.filter(group = 2), required=False, widget=FilteredSelectMultiple('tags', False ))
        def __init__(self, *args, **kwargs):
            super(AlbumAdminForm, self).__init__(*args, **kwargs)
            self.fields['tags_2'].widget.attrs['id'] = 'id_tags'

I couldn't change it's name or 'SelectFilter.init' call. Don't know if it would be any useful if I could. So, still no luck.

I think that it would be useful. maybe try widgets attrs
 

Third approach

Trying to use MultiWidget and MultiValueField:

    class My(object):
        def __init__(self, val1=EmptyQuerySet(), val2=EmptyQuerySet):
            self.val1=val1
            self.val2=val2

    class MyWidget(widgets.MultiWidget):
        def __init__(self, attrs=None):
            widget = (
               FilteredSelectMultiple('tags1', False),
               FilteredSelectMultiple('tags2', False)
               )
            super(MyWidget, self).__init__(widget, attrs=attrs)
        def decompress(self,value):
            if value:
                return value.val1 + value.val2
            return None

    class MyField(forms.MultiValueField):
        widget = MyWidget
        def __init__(self, required=True, widget=None, label=None, initial=None, help_text=None, queryset=None):
            field = (
                 ModelMultipleChoiceField(queryset=queryset),
                 ModelMultipleChoiceField(queryset=queryset)
                 ) 
            super(MyField, self).__init__(fields=field, widget=widget, label=label, initial=initial, help_text=help_text)

    class AlbumAdminForm(forms.ModelForm):
        class Meta:
            model = Album
        tags = MyField(queryset=Tag.objects.all())

In this case I have two forms but both are empty, I have nothing to choose. No luck =(

Are you sure you use the good parameter to pass the queryset to the underlying Field

An hack that could probably work is the create a copy the tags field into a tags_2 field in class model definition but it's kind of ugly ;)

Sammael

unread,
Jan 15, 2013, 5:22:27 AM1/15/13
to django...@googlegroups.com
вторник, 15 января 2013 г., 5:56:51 UTC+4 пользователь Amirouche написал:

Why are you creating two groups of tags ? Why not use a specific widgets like a tree widget ?

Great idea! Think, I'll give this a try. 


I think that it would be useful. maybe try widgets attrs

This:
     class AlbumAdminForm(forms.ModelForm):
       class Meta:
          model = Album
          widgets = {
             'tags_2': FilteredSelectMultiple('tags', False, attrs={'name': 'tags', 'id':'id_tags'}),
          }
       tags = forms.ModelMultipleChoiceField(queryset=Tag.objects.filter(public = True), required=False, widget=FilteredSelectMultiple('tags', False ))
       tags_2 = forms.ModelMultipleChoiceField(queryset=Tag.objects.filter(public = False), required=False, widget=FilteredSelectMultiple('tags', False ))

didn't help at all.

This:
    class AlbumAdminForm(forms.ModelForm):
       class Meta:
          model = Album
       tags = forms.ModelMultipleChoiceField(queryset=Tag.objects.filter(public = True), required=False, widget=FilteredSelectMultiple('tags', False ))
       tags_2 = forms.ModelMultipleChoiceField(queryset=Tag.objects.filter(public = False), required=False, widget=FilteredSelectMultiple('tags', False, attrs={'name': 'tags', 'id':'id_tags'}))
changes the 'id' attribute but not the 'name'.

Are you sure you use the good parameter to pass the queryset to the underlying Field

It's just an example with the same queryset for both fields. 

An hack that could probably work is the create a copy the tags field into a tags_2 field in class model definition but it's kind of ugly ;)

Indeed, it is. Additionally, I don't know beforehand how many tag groups there would be.


Amirouche, thank you for your answer, I really appreciate your help.

Amirouche Boubekki

unread,
Jan 15, 2013, 7:34:36 AM1/15/13
to django...@googlegroups.com
Why are you creating two groups of tags ? Why not use a specific widgets like a tree widget ?

Great idea! Think, I'll give this a try. 

It's not the same UX though...
 

Are you sure you use the good parameter to pass the queryset to the underlying Field

It's just an example with the same queryset for both fields. 

The example in the code is not working, «My» class is not referenced anywhere. I'm not familiar with multi-widgets but if a tree widgets is not what you want or don't get it work, I think it's the right approach. 

Amirouche, thank you for your answer, I really appreciate your help.

;)

Sammael

unread,
Jan 16, 2013, 7:36:16 AM1/16/13
to django...@googlegroups.com

вторник, 15 января 2013 г., 16:34:36 UTC+4 пользователь Amirouche написал:

The example in the code is not working, «My» class is not referenced anywhere. I'm not familiar with multi-widgets but if a tree widgets is not what you want or don't get it work, I think it's the right approach. 

Unfortunately, I don't know how it should be. Also I believe your idea of trees is much simpler and cleaner and thus much better.

I've installed django-mptt, and it works like a charm. But I'm worried it would be difficult to upgrade to a new Django version if I use third-party applications. So I was trying to implement similar approach using ForeignKey to self, just like django-mptt does:

    class Tag(models.Model):
        name = models.CharField(max_length=50, unique=True, db_index=True)
        parent = models.ForeignKey('self', null=True, blank=True, related_name='children')
        def __unicode__(self):
            if self.parent: return '%s%s' % ('-', self.name)
            return self.name

    class AlbumAdminForm(forms.ModelForm):
        class Meta:
             model = Album
        tags = forms.ModelMultipleChoiceField(queryset=getqueryset(), required=False, widget=FilteredSelectMultiple('tags', False ))

getqueryset() should return a QuerySet Object, sorted like this:
   parent1
   child1_of_parent1
   child2_of_parent1
   parent2
   child1_of_parent2
   child2_of_parent2
   
but I couldn't solve this yet. Concatenating of QuereSets with | operation drove me nowhere (result of | doesn't respect an order of concatenation) and QuerySet.annotate() didn't help either. Also no luck with sorted(): it returns some iterable object which is not a QuerySet, and Django fails while  rendering a template.
It's quite simple to get id list sorted in the right order:
    def getalltags():
        ids = []
        cats = Tag.objects.filter(parent__isnull=True).order_by('name')
        for id in cats.values_list('id', flat=True):
            ids += [id]
            tags = Tag.objects.filter(parent=id).order_by('name')
            ids += tags.values_list('id', flat=True)

But I couldn't get a QuerySet sorted this way yet. So I still keep thinking.

Thank you again for all your help.

Amirouche Boubekki

unread,
Jan 16, 2013, 8:01:21 AM1/16/13
to django...@googlegroups.com

2013/1/16 Sammael <s4m...@gmail.com>

Unfortunately, I don't know how it should be. Also I believe your idea of trees is much simpler and cleaner and thus much better.

I've installed django-mptt, and it works like a charm. But I'm worried it would be difficult to upgrade to a new Django version if I use third-party applications. So I was trying to implement similar approach using ForeignKey to self, just like django-mptt does:

Sorry, I wasn't clear, I was thinking only thinking about a tree widget not a full blown hierarchical tags which can be indeed a pain.

like the following:

at least jquery-tree-select use only one select element in html + javascript, so in python it's just a matter a formatting properly the options:

« [...] transforms it into a select box that just shows (none), Category 1, and Category 2. Then when you click on Category 1, a new select box pops up showing Subcategory 1 and Subcategory 2.»

HTH

Sammael

unread,
Jan 18, 2013, 1:01:41 AM1/18/13
to django...@googlegroups.com
Amirouche, thank you very much.
jQuery solution is quite interesting but it's more complicated for me to implement. So I think I gonna use your first idea =). django-mptt is very simple to install and it's well documented.
Thank you very much. Your help is inestimable.

среда, 16 января 2013 г., 17:01:21 UTC+4 пользователь Amirouche написал:

Andre Terra

unread,
Jan 18, 2013, 10:05:43 AM1/18/13
to django...@googlegroups.com
I'd just like to emphasize that django-mptt is *the* way to go. It's one great application that make it a breeze to work with hierarchies.


Cheers,
AT

--
You received this message because you are subscribed to the Google Groups "Django users" group.
To view this discussion on the web visit https://groups.google.com/d/msg/django-users/-/W0NeT7ZwS28J.

To post to this group, send email to django...@googlegroups.com.
To unsubscribe from this group, send email to django-users...@googlegroups.com.
For more options, visit this group at http://groups.google.com/group/django-users?hl=en.

Reply all
Reply to author
Forward
0 new messages