I have a model like this:
#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')
I decided in my urls to uniquely identify each Location through its own name, and the name of its parent-element.
# 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
As you can see from the examples, Locations where parent_location
is null/None are possible. I decided to convert None
to "None"
for these urls, though I'm not set on what None
gets transformed to. Now as you may realize this means that whenever I do anything with Location, I need to pay attention to convert "None"
to None
in the views as necessary, which gets annoying really fast.
To follow DRY I was wondering what the best way would be to deal with this, or have I designed myself into a corner here?
Edit: I believe the best solution for this might be within the Location model or its Manager. Fixing it there means this logic is handled for all views and queries, whereas a solution for this problem on the view-level would mean that the solution needs to be implemented for every view that might make a query to Location with `"None".
What I came up with so far:
Given that Managers are the interface through which you interact with models I checked whether they have anything and stumbled over modifying get_queryset. As I do not call get_queryset myself however, I'm not sure if functions like filter/exclude any others I might not even know about actually call it. However, I generally like this approach best as it keeps the logic on how to handle this stuff with the model/manager, where it belongs in my eyes.
Another alternative would of course be to write my own Mixin to fix this issue on the view-level, but that means I still need to remember every time that this is an issue.
Thanks to the advice of @Aayush Agrawal I just barely escaped from committing to a bad practice in this regard. In hindsight I agree, it is not a good idea to use a Manager for this, as while that might work, it would also obfuscate inputs and produce unexpected results in the future.
Thus I opted to go for the Mixin solution and Implemented the following:
# 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