【问题标题】:How can I make Stackoverflow-style URLs in Django with a working reverse, that allow the slug to change?如何在 Django 中使用反向工作来制作 Stackoverflow 样式的 URL,以允许 slug 更改?
【发布时间】:2015-09-09 07:41:11
【问题描述】:

如何制作一个与 Stackoverflow 类似的 URL 方案?

这与this one 不是同一个问题,尽管它是相似的。不同之处在于我想要一个实现 Stackoverflow 的所有聪明才智的 URL 方案,并允许 reverseing 生成完全 slugged 的​​ URL。

具体来说,要模仿的 Stackoverflow 行为:

  1. 仅按 id 查找,slug 仅用于 SEO/可读性目的
  2. 在不正确的 slug 或没有给出 slug 时转发到更正的 slug。例如如果我有一个名称为My Object Name 的对象123

    /123/ redirects to /123/my-object-name/
    /123/something/ redirects to /123/my-object-name/
    
  3. 如果我将对象名称更改为 My New Object Name,则重定向目标 slug 会相应更改(这是 Stackoverflow 的行为,如果您编辑问题的标题),例如:

    /123/my-object-name/ redirects to /123/my-new-object-name/
    
  4. 反向工作,使{% url 'my_view' 123 %}返回/123/my-object-name/,编辑对象名后返回/123/my-new-object-name/

我用models.py 破解了一些东西:

class MyModel(models.Model):
    name = models.CharField(max_length=70)

    def my_slugged_url(self):
        slug = slugify(self.name)
        if slug:
            return reverse('my_view', args=[self.id]) + slug + "/"
        return reverse('my_view', args=[self.id])

...和urls.py 模式:

url(r'^(\d+)/\S+/$', 'my_view')
url(r'^(\d+)/$', 'my_view'),

...还有一个views.py:

def my_view(request, id):
    obj = get_object_or_404(MyModel, pk=id)
    if request.path != obj.my_slugged_url():
        return redirect(obj.my_slugged_url())

...但这感觉不对,这意味着当我执行reverse{% url 'my_view' 123 %} 时,它会返回一个类似/123/ 的URL,然后必须重定向到/123/my-object-name

我怎样才能像 Stackoverflow 一样进行这项工作?

【问题讨论】:

  • 也许给这两种模式不同的名字?
  • 问题是,带有 slug 的模式 (url(r'^(\d+)/\S+/$', 'my_view')) 怎么知道要返回什么 slug?
  • @awidgery 这个问题可能会有所帮助 - Django optional URL patterns

标签: python django django-urls


【解决方案1】:

鉴于您反复出现的评论 - “...模式如何知道要返回哪个 slug”,您似乎无法理解它的工作原理。我会试着为你分解这个过程。

首先,您将编写两个指向一个视图的 url 模式。记得给两种模式不同的name

# urls.py
...
url(r'^(?P<object_id>\d+)/$', 'my_view', name='my-view-no-slug'),
url(r'^(?P<object_id>\d+)/(?P<slug>\S+)/$', 'my_view', name='my-view-slug'),
...

现在,这就是有趣的地方:

  1. 每当向/123/ 发出请求时,它都会匹配第一个url 模式。
  2. 当向/123/my-object-name/ 发出请求时,它将匹配第二个模式。
  3. 当使用错误的 slug 发出请求时,例如 - /123/some-wrong-slug/,它也会匹配第二个模式。不用担心,您会在视图中检查错误的 slug。

但是所有三个请求都将由一个视图处理。

其次,在模型中定义一个名为 slugproperty。您将使用它来生成和访问对象的 slug。

# models.py

class MyModel(...):
    ...

    @property
    def slug(self):
        return slugify(self.name)

    def get_absolute_url(self):
        return reverse('my-view-slug', args=[self.id, self.slug])

最后,处理请求的视图应该如下所示:

# views.py

def my_view(request, object_id, slug=None):
    # first get the object
    my_object = get_object_or_404(MyModel, id=object_id)

    # Now we will check if the slug in url is same 
    # as my_object's slug or not
    if slug != my_object.slug:
        # either slug is wrong or None
        return redirect(my_object.get_absolute_url())

    # this is processed if slugs match
    # so do whatever you want
    return render(request, 'my-template.html', {'my_object': my_object})

我希望这可以清楚地说明如何实现 StackOverflow-like url 行为。

【讨论】:

    【解决方案2】:
    # views
    def detail(request, object_id, slug):
        obj = get_object_or_404(MyModel, pk=object_id)
        if obj.slug != slug:
            canonical = obj.get_absolute_url()
            return redirect(canonical)
    
        context = {"obj":obj}
        return render(request, "myapp/detail.html", context)
    
    
    # urls
    from myapp.views import detail
    urlpatterns = ('',
        #...
        url(r'^(?P<object_id>\d+)/(<?P<slug>\S+)/$', detail, name="detail")
        url(r'^(\d+)/$', lambda request, pk: detail(request, pk, None), name="redirect-to-detail"),
        # ...
        )
    
    # models
    class MyModel(models.Model):
        def get_absolute_url(self):
            return reverse(
              "detail", 
              kwargs=dict(object_id=self.id, slug=self.slug)
              )
    
        @property
        def slug(self):
            return slugify(self.title)
    

    【讨论】:

    • 不需要lambda,只需在视图函数中使用slug=None作为默认值。
    • 您还缺少patterns()。我会推荐一个没有前缀的普通列表,因为 patterns 已被弃用。
    • 我不确定这将如何与 {% url 'detail' 123 %}'? From my tests it returns /2/x/` 一起使用,因为它不知道 slug 是什么
    • 使用{% url 'detail' obj.id obj.slug %} 或简单的{{ obj.get_absolute_url }}
    • @knbk :是的,从技术上讲,在视图中使用 slug 的默认值更简单。我想说的是,使用 lambda 会使意图更加明确,但 YMMV
    猜你喜欢
    • 2011-03-05
    • 2018-02-12
    • 1970-01-01
    • 2011-04-20
    • 1970-01-01
    • 1970-01-01
    • 2013-08-11
    • 2016-01-10
    • 1970-01-01
    相关资源
    最近更新 更多