Repository: pulp/pulpcore

Issue:
Race in DistributionWithContentFilter can lead to 500
The only possible explanation I can find is that cast is messing up the related prefetched models, while acutally not be needed.
All we need is the class of the distribution. Not the detail instance.

``` python
    for dist in qs.exclude(repository=None).prefetch_related("repository__versions"):
        if dist.cast().SERVE_FROM_PUBLICATION:
            versions = dist.repository.versions.values_list("pk", flat=True)
            ....
```

```
pulp [c19d4bd0-3b4c-4d3a-87fb-9cd2b5c2edd2]: django.request:ERROR: Internal Server Error: /pulp/api/v3/distributions/file/file/
Traceback (most recent call last):
  File "/usr/local/lib/python3.8/site-packages/django/core/handlers/exception.py", line 47, in inner
    response = get_response(request)
  File "/usr/local/lib/python3.8/site-packages/django/core/handlers/base.py", line 181, in _get_response
    response = wrapped_callback(request, *callback_args, **callback_kwargs)
  File "/usr/local/lib/python3.8/site-packages/django/views/decorators/csrf.py", line 54, in wrapped_view
    return view_func(*args, **kwargs)
  File "/usr/local/lib/python3.8/site-packages/rest_framework/viewsets.py", line 125, in view
    return self.dispatch(request, *args, **kwargs)
  File "/usr/local/lib/python3.8/site-packages/rest_framework/views.py", line 509, in dispatch
    response = self.handle_exception(exc)
  File "/usr/local/lib/python3.8/site-packages/rest_framework/views.py", line 469, in handle_exception
    self.raise_uncaught_exception(exc)
  File "/usr/local/lib/python3.8/site-packages/rest_framework/views.py", line 480, in raise_uncaught_exception
    raise exc
  File "/usr/local/lib/python3.8/site-packages/rest_framework/views.py", line 506, in dispatch
    response = handler(request, *args, **kwargs)
  File "/usr/local/lib/python3.8/site-packages/rest_framework/mixins.py", line 38, in list
    queryset = self.filter_queryset(self.get_queryset())
  File "/usr/local/lib/python3.8/site-packages/rest_framework/generics.py", line 150, in filter_queryset
    queryset = backend().filter_queryset(self.request, queryset, self)
  File "/usr/local/lib/python3.8/site-packages/django_filters/rest_framework/backends.py", line 74, in filter_queryset
    return filterset.qs
  File "/usr/local/lib/python3.8/site-packages/django_filters/filterset.py", line 230, in qs
    qs = self.filter_queryset(qs)
  File "/usr/local/lib/python3.8/site-packages/django_filters/filterset.py", line 213, in filter_queryset
    queryset = self.filters[name].filter(queryset, value)
  File "/usr/local/lib/python3.8/site-packages/pulpcore/app/viewsets/custom_filters.py", line 424, in filter
    versions = dist.repository.versions.values_list("pk", flat=True)
AttributeError: 'NoneType' object has no attribute 'versions'
```


Discussion:

Resolving patch:
diff --git a/pulpcore/app/models/base.py b/pulpcore/app/models/base.py
--- a/pulpcore/app/models/base.py
+++ b/pulpcore/app/models/base.py
@@ -148,16 +148,20 @@ def get_pulp_type(cls):
     def get_model_for_pulp_type(cls, pulp_type):
         return cls._pulp_model_map[pulp_type]
 
-    def save(self, *args, **kwargs):
+    @property
+    def detail_model(self):
+        return self._pulp_model_map[self.pulp_type]
+
+    def __init__(self, *args, **kwargs):
         # instances of "detail" models that subclass MasterModel are exposed
         # on instances of MasterModel by the string stored in that model's TYPE attr.
         # Storing this pulp_type in a column on the MasterModel next to makes it trivial
         # to filter for specific detail model types across master's relations.
         # Prepend the TYPE defined on a detail model with a django app label.
         # If a plugin sets the type field themselves, it's used as-is.
+        super().__init__(*args, **kwargs)
         if not self.pulp_type:
             self.pulp_type = self.get_pulp_type()
-        return super().save(*args, **kwargs)
 
     def cast(self):
         """Return the "Detail" model instance of this master-detail object.
diff --git a/pulpcore/app/viewsets/custom_filters.py b/pulpcore/app/viewsets/custom_filters.py
--- a/pulpcore/app/viewsets/custom_filters.py
+++ b/pulpcore/app/viewsets/custom_filters.py
@@ -355,12 +355,12 @@ def filter(self, qs, value):
         for dist in qs.exclude(publication=None).values("publication__repository_version", "pk"):
             versions_distributions[dist["publication__repository_version"]].append(dist["pk"])
 
-        for dist in qs.exclude(repository_version=None).values("repository_version", "pk"):
-            if not dist.cast().SERVE_FROM_PUBLICATION:
+        for dist in qs.exclude(repository_version=None).values("repository_version", "pulp_type"):
+            if not dist.detail_model.SERVE_FROM_PUBLICATION:
                 versions_distributions[dist["repository_version"]].append(dist["pk"])
 
         for dist in qs.exclude(repository=None).prefetch_related("repository__versions"):
-            if dist.cast().SERVE_FROM_PUBLICATION:
+            if dist.detail_model.SERVE_FROM_PUBLICATION:
                 versions = dist.repository.versions.values_list("pk", flat=True)
                 publications = Publication.objects.filter(
                     repository_version__in=versions, complete=True


Test patch: