Django - 如何在不重复自己的情况下将 "None" 从 url kwargs 转换为 None

Django - How to cast "None" from url kwargs to None without repeating yourself

我有这样一个模型:

#models.py
class Location(BaseArticle):
    name = models.CharField(max_length=200)
    parent_location = models.ForeignKey("self",
                                        blank=True,
                                        null=True,
                                        help_text="Fill in if this location is a smaller part of another location.",
                                        on_delete=models.SET_NULL)
    description = HTMLField(blank=True,
                            null=True,
                            help_text="A description of the location and important features about it")

    class Meta:
        unique_together = ('name', 'parent_location')

我决定在我的 url 中通过自己的名称及其父元素的名称来唯一标识每个位置。

# urls.py
urlpatterns = [
    path('locations/<str:parent_location_name>/<str:location_name>', wiki_views.LocationView.as_view(), name='location'),
]

# example url to location "Seedy Bar" located in "City"
# https://<DOMAIN>/locations/city/seedy bar
# example url to location "City" that does not have a parent_location
# https://<DOMAIN>/locations/none/city

正如您从示例中看到的,parent_location 为 null/None 的位置是可能的。我决定将这些 url 的 None 转换为 "None",尽管我没有设置 None 转换成什么。现在你可能意识到这意味着每当我对 Location 做任何事情时,我需要注意根据需要在视图中将 "None" 转换为 None,这很快就会变得很烦人。

为了关注 DRY,我想知道处理这个问题的最佳方法是什么,还是我把自己设计成了一个角落?

编辑:我认为最好的解决方案可能是在 Location 模型或其管理器中。在那里修复它意味着为所有视图和查询处理此逻辑,而在视图级别解决此问题将意味着需要为每个可能使用“[=44 查询 Location 的视图实施该解决方案=]".

到目前为止我想到了什么:

鉴于管理器是您与模型交互的界面,我检查了它们是否有任何内容并无意中修改了 get_queryset。但是,由于我自己不调用 get_queryset,所以我不确定是否有像 filter/exclude 这样的函数,我什至不知道是否真的调用了它。但是,我通常最喜欢这种方法,因为它保留了如何使用 model/manager 处理这些东西的逻辑,它属于我的眼中。

另一种选择当然是编写我自己的 Mixin 以在视图级别修复此问题,但这意味着每次我仍然需要记住这是一个问题。

我会这样做:

创建两个 URL:

path('locations/<str:location_name>', wiki_views.LocationView.as_view(), name='location'),
path('locations/<str:parent_location_name>/<str:location_name>', wiki_views.LocationViewWithParent.as_view(), name='location'),

并像这样构造这两个视图:

class LocationView(generics.APIView):
    parent = None
    ... rest of your code ...

class LocationViewWithparent(LocationView):
    parent = #Your code to get the parent_location_name parameter

第二个视图将执行与父视图相同的所有操作,但会覆盖父变量

多亏了@Aayush Agrawal 的建议,我才勉强避免了在这方面犯下的错误做法。事后看来,我同意,为此使用管理器不是一个好主意,因为虽然这可能有效,但它也会混淆输入并在将来产生意想不到的结果。

因此我选择了 Mixin 解决方案并实现了以下内容:

# settings.py
NONE_STRING = 'None'



# views.py
from django.conf import settings

class ReplaceNoneMixin(object):
    """Mixin to deal with URL Parameters that represent None. This mixin takes the name of a url parameter and assumes
    that if it represents None that it has the value of settings.NONE_STRING. If the parameter is encountered with that
    value, the value is changed from NONE_STRING to None."""
    kwargs: dict
    noneable_parameters_keys: list

    def setup(self, request, *args, **kwargs):
        """Changes the value of all url parameters specified in noneable_parameters_keys list. If the parameter has the
         settings.NONE_STRING value in kwargs then the value is set to None."""
        super().setup(request, *args, **kwargs)
        if isinstance(self.noneable_parameters_keys, str):
            self.noneable_parameters_keys = [self.noneable_parameters_keys]

        for parameter in self.noneable_parameters_keys:
            if parameter in self.kwargs and self.kwargs[parameter] == settings.NONE_STRING:
                self.kwargs[parameter] = None