-
-
Notifications
You must be signed in to change notification settings - Fork 6.9k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Document the use of get_queryset
for RelatedField
#3605
Document the use of get_queryset
for RelatedField
#3605
Conversation
My use-case for this feature is overriding |
py33: python3.3 | ||
py34: python3.4 | ||
py35: python3.5 | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Any reason this change is in the PR ?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It seemed uncontroversial. If it is, I'm happy to remove it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I have it in a separate commit, and have some commentary on why I did it in the commit message.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@ryanhiebert that does seem valid, but we suggest perhaps submitting that as a separate PR. That way we can discuss and progress both separately.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done. #3606. I've removed that commit from this PR.
The user may wish to provide a dynamic queryset on a `RelatedField` based on the `context`. The way to do that is to create a subclass of `RelatedField` (or a child) and override the `get_queryset` method. However, this is undocumented, and instantiating that field without a `queryset` argument (because it's not needed) will raise an assertion error. Document `.get_queryset(self)` as an official part of the API of `RelatedField`, and don't enforce the use of `queryset` when `get_queryset` is overridden.
It's not clear to me how this change impacts the discovery of the misconfiguration. |
@xordoquy : it delays the assertion so that subclasses can override This is the actual code of my subclass: class EndpointAdvisorSlugField(SlugRelatedField):
def get_queryset(self):
endpoint_name = self.context['view'].kwargs['endpoint_name']
return EndpointUser.objects.filter(
is_advisor=True,
endpoint__name=endpoint_name,
) I didn't need any special behavior from the advisor_ids = EndpointAdvisorSlugField(
# The queryset argument is needed for validation to work.
source='advisors', many=True, slug_field='lms_id', queryset=True) |
@xordoquy : re-reading your comment, I think I didn't properly address your question. Yes, moving the assertion into the default What I might be able to do is check whether the |
Perhaps this would work: class RelatedField(Field):
...
def __init__(self, *args, **kwargs):
...
default_get_queryset = getattr(RelatedField.get_queryset, '__func__',
RelatedField.get_queryset) # Py 3 compat
if self.get_queryset.__func__ == default_get_queryset:
assert self.queryset is not None or kwargs.get('read_only', None), (
'Relational field must provide a `queryset` argument, '
'or set read_only=`True`.'
) Assuming it works the way I intend, it checks to see if the method has been overridden before running the assertion. |
I went ahead and added that solution. That should make it so that |
First impressions? Not convinced that I'm in favor of the complexity here. We might want to prefer better documentation and support for custom fields. Alternative to this - why not override |
@timchristie: Overriding at instantiation is not sufficient because I need to take into account the Overriding the already-documented methods isn't desirable, because there's no way for me to customize the queryset obtained without re-implementing all the logic of those methods. I'm in particular using |
My meaning was rather that |
Overriding the On making it more obvious: I could see that functionality being useful elsewhere, so one option would be to make it a utility function: def method_overridden(method_name, klass, instance):
"""
Check if a method has been overridden.
"""
method = getattr(klass, method_name)
default_method = getattr(method, '__func__', method) # Python 3 compat
return default_method is not getattr(instance, method_name).__func__ which could be used simply in this case as: if not method_overridden('get_queryset', RelatedField, self):
# Check to make sure that self.queryset is given. Another option that may be able to avoid a utility function could be to add a marker to the default method: class RelatedField(Field):
...
def __init__(self, *args, **kwargs):
...
if not getattr(self.get_queryset, 'overridden', True):
# Check to make sure that self.queryset is given
def get_queryset(self, *args, **kwargs);
...
get_queryset.overridden = False I'm not sure it's sufficiently more readable than the other option. I'm probably inclined toward making the utility function to try and improve readability. It might be possible to make it a method on |
is |
@tomchristie : Not by itself, because it's wrapped in a bound method object. If you dig into the |
""" | ||
method = getattr(klass, method_name) | ||
default_method = getattr(method, '__func__', method) # Python 3 compat | ||
return default_method is not getattr(instance, method_name).__func__ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's avoid introducing a module if poss.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sure thing. It can go in another module, but I'm not sure where it fits. Perhaps just put it in relations.py
for now?
if not method_overridden('get_queryset', RelatedField, self): | ||
assert self.queryset is not None or kwargs.get('read_only', None), ( | ||
'Relational field must provide a `queryset` argument, ' | ||
'or set read_only=`True`.' |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should this now read "Relational field must provide a queryset
argument, override get_queryset
, 'or set read_only=True
."?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sounds like a good idea.
Is there anything else that I should do for this PR? |
@ryanhiebert looks good, sorry for the delay. |
RelatedField get_queryset and context
@@ -176,6 +176,14 @@ def test_representation(self): | |||
representation = self.field.to_representation(self.instance) | |||
assert representation == self.instance.name | |||
|
|||
def test_no_queryset_init(self): | |||
class NoQuerySetSlugRelatedField(serializers.SlugRelatedField): | |||
def get_queryset(this): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Shouldn't be self
instead of this
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks @ticosax it indeed should be.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe not, I need to dig this a bit.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No, because I needed to get to self
from the test case inside this method.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It perhaps would have been better to use staticmethod
, or at least add a comment about why it was like that.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Refactored slightly in #3861
get_queryset
for RelatedField
Currently
get_queryset
is not a part of the documented API ofRelatedField
. This pull request makes it a documented part of the API, and adds a test using that feature with a subclass ofSlugRelatedField
.