

Djangoを用いてhttps://www.gumtree.com.auのサイドメニューにある絞込検索を実装したいと思っています。  
例としてこのサイトでグローバルメニューにあるJobsをクリックするとサイドメニューに大カテゴリーであるJobsの小カテゴリーが表示されます。また小カテゴリーを選択するとそれに応じたPostが中央にリストで表示されます。  
この時点でURLは/BigCategory/、または/SmallCategory/となっています。

また下のようなカテゴリー欄があり

大カテゴリー  
｜ー小カテゴリー  
｜ー小カテゴリー

この下には

State  
｜ーRegion  
｜ーRegion

と位置の絞込ができるようになっています。  
もしStateを選択すると今選択しているカテゴリー（大カテゴリーか小カテゴリー）に応じて/BigCategory/State/か/SmallCategory/State/でPostが表示されます。  
Regionを選択した場合は/BigCategory/Region/か/SmallCategory/Region/となります。

これらのことを実装したいのですが

上記のURLについてまとめると/Category/Location/となりますが、これを自分で考えて実装してみたところ、

urls.py

[code]

    urlpatterns = [
        url(r'^(?P<cat>[-\w]+)/(?P<loc>[-\w]+)/$', views.loc_index, name='index'),
        url(r'^(?P<cat>[-\w]+)/$', views.cat_index, name='index'),
    ]
    
[/code]

models.py

[code]

    class BigCategory(models.Model):
        name = models.CharField(max_length=255)
        slug = models.SlugField()
    
        def __str__(self):
            return self.name
    
    class SmallCategory(models.Model):
        parent = models.ForeignKey(BigCategory, related_name='children')
        name = models.CharField(max_length=255)
        slug = models.SlugField()
    
        def __str__(self):
            return self.name
    
    class State(models.Model):
        name = models.CharField(max_length=255)
        slug = models.SlugField()
    
        def __str__(self):
            return self.name
    
    class Region(models.Model):
        parent = models.ForeignKey(State, related_name='children')
        name = models.CharField(max_length=255)
        slug = models.SlugField()
    
        def __str__(self):
            return self.name
    
    class Post(models.Model):
        category = models.ForeignKey(SmallCategory)
        location = models.ForeignKey(Region)
        title = models.CharField(max_length=255)
        text = models.CharField(max_length=255)
    
        def __str__(self):
            return self.title
    
[/code]

views.py

[code]

    from .models import BigCategory, SmallCategory, State, Region, Post
    
    def cat_index(request, cat):
    
        if BigCategory.objects.filter(slug=cat):
            # cat == big
            big_cat = BigCategory.objects.get(slug=cat)
            states = State.objects.all()
            posts = Post.objects.filter(category__parent__slug=cat)
            c = {'big_cat': big_cat, 'states': states, 'posts': posts}
    
        elif SmallCategory.objects.filter(slug=cat):
            # cat == small
            big_cat = BigCategory.objects.get(children__slug=cat)
            states = State.objects.all()
            posts = Post.objects.filter(category__slug=cat)
            c = {'big_cat': big_cat, 'states': states, 'posts': posts}
    
        else:
            # raise 404
    
        return render(request, 'classifieds/index.html', c)
    
    def loc_index(request, cat, loc):
    
        if BigCategory.objects.filter(slug=cat):
            # cat == big
            big_cat = BigCategory.objects.get(slug=cat)
            selected_cat = 'big'
    
            if State.objects.filter(slug=loc):
                state = State.objects.get(slug=loc)
                posts = Post.objects.filter(category__parent__slug=cat, location__parent__slug=loc)
                c = {'big_cat': big_cat, 'selected_cat': selected_cat, 'state': state, 'posts': posts}
    
            elif Region.objects.filter(slug=loc):
                state = State.objects.get(children__slug=loc)
                posts = Post.objects.filter(category__parent__slug=cat, location__slug=loc)
                c = {'big_cat': big_cat, 'selected_cat': selected_cat, 'state': state, 'posts': posts}
    
            else:
                print('no location')
    
        elif SmallCategory.objects.filter(slug=cat):
            # cat == small
            big_cat = BigCategory.objects.get(children__slug=cat)
            selected_cat = 'small'
            small_cat_for_loc = SmallCategory.objects.get(slug=cat)
    
            if State.objects.filter(slug=loc):
                state = State.objects.get(slug=loc)
                posts = Post.objects.filter(category__slug=cat, location__parent__slug=loc)
                c = {'big_cat': big_cat, 'small_cat_for_loc': small_cat_for_loc, 'state': state, 'posts': posts}
    
            elif Region.objects.filter(slug=loc):
                state = State.objects.get(children__slug=loc)
                posts = Post.objects.filter(category__slug=cat, location__slug=loc)
                c = {'big_cat': big_cat, 'small_cat_for_loc': small_cat_for_loc, 'state': state, 'posts': posts}
    
            else:
                print('no location')
    
        else:
            # raise 404
    
        return render(request, 'classifieds/index.html', c)
    
[/code]

このようになりましたが、将来的にカテゴリーや位置だけでなく他のオプションである絞込も実装しようとしたときにいちいち全ての場合分けをしていたらとてもわかりづらく長いコードになってしまいます。

ご教授頂きたいのは、

①URLの場合分けの方法  
/BigCategory/SmallCategory/State/Region/とすれば実装は今よりはるかに楽になるのですが、/BigCategory or
SmallCategory/State or
Region/とすればURLも短くなるのでこのようにしたいです。これを実現するために理想的なviewの書き方をご教授ください。

②上記に記載したコードは私にまだDjangoの知識がなく、がむしゃらに書く殴ったものなのでコード全体、そしてシステム自体の見直しをするにあたっての手ほどきをお願い致します。  
例えばカスタムテンプレートタグをこんな風に使えばより楽になる、等

理想は冒頭に記載した参考サイトです。  
どうかお助けください。


CC BY-SA 3.0
2017-03-19T05:41:28.373
33407
2018-04-10T06:48:14.873
21114
question
python django
Djangoの絞込検索について


以下のように考えるとシンプルに実装できそうな気がします。  
それぞれのモデルの違いは下記のようになっています。

[code]

    1. BigCategory と SmallCategory は parentを持つか否か
    2. State と Region は parentを持つか否か
    
[/code]

この違いを考慮した上で、それぞれを一つのモデルにまとめると良さそうです。

[code]

    class Category(models.Model):
        # BigCategory と SmallCategory 両方の役割を持つ
        parent = models.ForeignKey(Category, related_name='children', null=True, blank=True)
        name = models.CharField(max_length=255)
        slug = models.SlugField()
    
        def __str__(self):
            return self.name
    
    class Location(models.Model):
        # State と Region 両方の役割を持つ
        parent = models.ForeignKey(State, related_name='children', null=True, blank=True)
        name = models.CharField(max_length=255)
        slug = models.SlugField()
    
[/code]

今までのデータは以下のようにして取得可能です。

[code]

    # BigCateogry (parentが無い)
    Category.objects.filter(parent__isnull=True)
    
    # SmallCategory
    Category.objects.filter(parent__isnull=False)
    
    # State (parentが無い)
    Location.objects.filter(parent__isnull=True)
    
    # Region
    Location.objects.filter(parent__isnull=False)
    
[/code]

あとはModelのメソッドやQuerySetに工夫をすることで、多数の条件分岐を書かずにviewを実装できそうです。以下に一例を貼ります。(実際に動かすことはしてないのでうまく動かないかもしれません。参考程度にとどめておいてください)

models.py

[code]

    class Category(models.Model):
        parent = models.ForeignKey(Category, related_name='children', null=True, blank=True)
        name = models.CharField(max_length=255)
        slug = models.SlugField()
    
        @property
        def big_cat(self):
            # parent が nullであれば親カテゴリなので自分自身を返す
            if not self.parent:
                return self
            # そうでなければ親カテゴリ(parent)を返す
            else:
                return self.parent
    
        def __str__(self):
            return self.name
    
    class Location(models.Model):
        parent = models.ForeignKey(State, related_name='children', null=True, blank=True)
        name = models.CharField(max_length=255)
        slug = models.SlugField()
    
        @property
        def state(self):
            # parent が nullであればStateなので自分自身を返す
            if not self.parent:
                return self
            # そうでなければState(parent)を返す
            else:
                return self.parent
    
        def __str__(self):
            return self.name
    
    class PostQuerySet(models.QuerySet):
    
        def filter_category(self, category):
            if not category.parent:
                # Big Category であれば、childrenのカテゴリに所属するPostをfilter
                return self.filter(category__in=category.children.all())
            else:
                # Small Category であれば、categoryに所属するPostをfilter
                return self.filter(category=category)
    
        def filter_location(self, location):
            if not location.parent:
                # State であれば、childrenのlocationに所属するPostをfilter
                return self.filter(location__in=location.children.all())
            else:
                # Region であれば そのlocationに所属するPostをfilter
                return self.filter(location=location)
    
    
    class Post(models.Model):
        category = models.ForeignKey(Category)
        location = models.ForeignKey(Location)
        title = models.CharField(max_length=255)
        text = models.CharField(max_length=255)
    
        objects = PostQuerySet.as_manager()
    
        def __str__(self):
            return self.title
    
[/code]

views.py

[code]

    from django.shortcuts import get_object_or_404
    
    from .models import Category, Location, Post
    
    
    def cat_index(request, cat):
        category = get_object_or_404(Category, slug=cat)
        return render(request, 'classifieds/index.html', {
            'big_cat': category.big_cat,
            'states': Location.objects.filter(parent__isnull=True).all(),
            'posts': Post.objects.filter_category(category).all()
        })
    
    
    def loc_index(request, cat, loc):
        category = get_object_or_404(Category, slug=cat)
        location = get_object_or_404(Location, slug=loc)
        ctx = {
            'big_cat': category.big_cat,
            'state': location.state,
            'posts': Post.objects.filter_category(category).filter_location(location).all()
        }
        if not category.parent:
            ctx.update({'selected_cat': 'big'})
        else:
            ctx.update({'small_cat_for_loc': category})
        return render(request, 'classifieds/index.html', ctx)
    
[/code]


CC BY-SA 3.0
2017-03-19T12:53:18.853
33412
2017-03-19T12:53:18.853
2168
33407
answer