Ejemplo n.º 1
0
    def _get_real_instances(self, base_result_objects):
        """
        Polymorphic object loader

        Does the same as:

            return [ o.get_real_instance() for o in base_result_objects ]

        but more efficiently.

        The list base_result_objects contains the objects from the executed
        base class query. The class of all of them is self.model (our base model).

        Some, many or all of these objects were not created and stored as
        class self.model, but as a class derived from self.model. We want to re-fetch
        these objects from the db as their original class so we can return them
        just as they were created/saved.

        We identify these objects by looking at o.polymorphic_ctype, which specifies
        the real class of these objects (the class at the time they were saved).

        First, we sort the result objects in base_result_objects for their
        subclass (from o.polymorphic_ctype), and then we execute one db query per
        subclass of objects. Here, we handle any annotations from annotate().

        Finally we re-sort the resulting objects into the correct order and
        return them as a list.
        """
        ordered_id_list = []  # list of ids of result-objects in correct order
        results = {
        }  # polymorphic dict of result-objects, keyed with their id (no order)

        # dict contains one entry per unique model type occurring in result,
        # in the format idlist_per_model[modelclass]=[list-of-object-ids]
        idlist_per_model = defaultdict(list)

        # - sort base_result_object ids into idlist_per_model lists, depending on their real class;
        # - also record the correct result order in "ordered_id_list"
        # - store objects that already have the correct class into "results"
        base_result_objects_by_id = {}
        self_model_content_type_id = ContentType.objects.get_for_model(
            self.model).pk
        for base_object in base_result_objects:
            ordered_id_list.append(base_object.pk)

            # check if id of the result object occeres more than once - this can happen e.g. with base_objects.extra(tables=...)
            assert not base_object.pk in base_result_objects_by_id, (
                "django_polymorphic: result objects do not have unique primary keys - model "
                + unicode(self.model))

            base_result_objects_by_id[base_object.pk] = base_object

            # this object is not a derived object and already the real instance => store it right away
            if (base_object.polymorphic_ctype_id == self_model_content_type_id
                ):
                results[base_object.pk] = base_object

            # this object is derived and its real instance needs to be retrieved
            # => store it's id into the bin for this model type
            else:
                idlist_per_model[base_object.get_real_instance_class()].append(
                    base_object.pk)

        # django's automatic ".pk" field does not always work correctly for
        # custom fields in derived objects (unclear yet who to put the blame on).
        # We get different type(o.pk) in this case.
        # We work around this by using the real name of the field directly
        # for accessing the primary key of the the derived objects.
        # We might assume that self.model._meta.pk.name gives us the name of the primary key field,
        # but it doesn't. Therefore we use polymorphic_primary_key_name, which we set up in base.py.
        pk_name = self.model.polymorphic_primary_key_name

        # For each model in "idlist_per_model" request its objects (the real model)
        # from the db and store them in results[].
        # Then we copy the annotate fields from the base objects to the real objects.
        # Then we copy the extra() select fields from the base objects to the real objects.
        # TODO: defer(), only(): support for these would be around here
        for modelclass, idlist in idlist_per_model.items():
            qs = modelclass.base_objects.filter(
                pk__in=idlist)  # use pk__in instead ####
            qs.dup_select_related(
                self)  # copy select related configuration to new qs

            for o in qs:
                o_pk = getattr(o, pk_name)

                if self.query.aggregates:
                    for anno_field_name in self.query.aggregates.keys():
                        attr = getattr(base_result_objects_by_id[o_pk],
                                       anno_field_name)
                        setattr(o, anno_field_name, attr)

                if self.query.extra_select:
                    for select_field_name in self.query.extra_select.keys():
                        attr = getattr(base_result_objects_by_id[o_pk],
                                       select_field_name)
                        setattr(o, select_field_name, attr)

                results[o_pk] = o

        # re-create correct order and return result list
        resultlist = [
            results[ordered_id] for ordered_id in ordered_id_list
            if ordered_id in results
        ]

        # set polymorphic_annotate_names in all objects (currently just used for debugging/printing)
        if self.query.aggregates:
            annotate_names = self.query.aggregates.keys(
            )  # get annotate field list
            for o in resultlist:
                o.polymorphic_annotate_names = annotate_names

        # set polymorphic_extra_select_names in all objects (currently just used for debugging/printing)
        if self.query.extra_select:
            extra_select_names = self.query.extra_select.keys(
            )  # get extra select field list
            for o in resultlist:
                o.polymorphic_extra_select_names = extra_select_names

        return resultlist
Ejemplo n.º 2
0
    def _get_real_instances(self, base_result_objects):
        """
        Polymorphic object loader

        Does the same as:

            return [ o.get_real_instance() for o in base_result_objects ]

        The list base_result_objects contains the objects from the executed
        base class query. The class of all of them is self.model (our base model).

        Some, many or all of these objects were not created and stored as
        class self.model, but as a class derived from self.model. We want to re-fetch
        these objects from the db as their original class so we can return them
        just as they were created/saved.

        We identify these objects by looking at o.polymorphic_ctype, which specifies
        the real class of these objects (the class at the time they were saved).

        First, we sort the result objects in base_result_objects for their
        subclass (from o.polymorphic_ctype), and then we execute one db query per
        subclass of objects. Here, we handle any annotations from annotate().

        Finally we re-sort the resulting objects into the correct order and
        return them as a list.
        """
        ordered_id_list = []    # list of ids of result-objects in correct order
        results = {}            # polymorphic dict of result-objects, keyed with their id (no order)

        # dict contains one entry per unique model type occurring in result,
        # in the format idlist_per_model[modelclass]=[list-of-object-ids]
        idlist_per_model = defaultdict(list)

        # - sort base_result_object ids into idlist_per_model lists, depending on their real class;
        # - also record the correct result order in "ordered_id_list"
        # - store objects that already have the correct class into "results"
        base_result_objects_by_id = {}
        self_model_content_type_id = ContentType.objects.get_for_model(self.model).pk
        for base_object in base_result_objects:
            ordered_id_list.append(base_object.pk)
            base_result_objects_by_id[base_object.pk] = base_object

            # this object is not a derived object and already the real instance => store it right away
            if (base_object.polymorphic_ctype_id == self_model_content_type_id):
                results[base_object.pk] = base_object

            # this object is derived and its real instance needs to be retrieved
            # => store it's id into the bin for this model type
            else:
                idlist_per_model[base_object.get_real_instance_class()].append(base_object.pk)

        # For each model in "idlist_per_model" request its objects (the real model)
        # from the db and store them in results[].
        # Then we copy the annotate fields from the base objects to the real objects.
        # TODO: defer(), only(): support for these would be around here
        for modelclass, idlist in idlist_per_model.items():
            qs = modelclass.base_objects.filter(id__in=idlist)
            qs.dup_select_related(self)    # copy select related configuration to new qs
            for o in qs:
                if self.query.aggregates:
                    for anno in self.query.aggregates.keys():
                        attr = getattr(base_result_objects_by_id[o.pk], anno)
                        setattr(o, anno, attr)
                results[o.pk] = o

        # re-create correct order and return result list
        resultlist = [ results[ordered_id] for ordered_id in ordered_id_list if ordered_id in results ]
        return resultlist
Ejemplo n.º 3
0
    def _get_real_instances(self, base_result_objects):
        """
        Polymorphic object loader

        Does the same as:

            return [ o.get_real_instance() for o in base_result_objects ]

        but more efficiently.

        The list base_result_objects contains the objects from the executed
        base class query. The class of all of them is self.model (our base model).

        Some, many or all of these objects were not created and stored as
        class self.model, but as a class derived from self.model. We want to re-fetch
        these objects from the db as their original class so we can return them
        just as they were created/saved.

        We identify these objects by looking at o.polymorphic_ctype, which specifies
        the real class of these objects (the class at the time they were saved).

        First, we sort the result objects in base_result_objects for their
        subclass (from o.polymorphic_ctype), and then we execute one db query per
        subclass of objects. Here, we handle any annotations from annotate().

        Finally we re-sort the resulting objects into the correct order and
        return them as a list.
        """
        ordered_id_list = []    # list of ids of result-objects in correct order
        results = {}            # polymorphic dict of result-objects, keyed with their id (no order)

        # dict contains one entry per unique model type occurring in result,
        # in the format idlist_per_model[modelclass]=[list-of-object-ids]
        idlist_per_model = defaultdict(list)

        # - sort base_result_object ids into idlist_per_model lists, depending on their real class;
        # - also record the correct result order in "ordered_id_list"
        # - store objects that already have the correct class into "results"
        base_result_objects_by_id = {}
        self_model_content_type_id = ContentType.objects.get_for_proxied_model(self.model).pk
        self_model_unproxied_content_type_id = ContentType.objects.get_for_model(self.model).pk
        for base_object in base_result_objects:
            ordered_id_list.append(base_object.pk)

            # check if id of the result object occeres more than once - this can happen e.g. with base_objects.extra(tables=...)
            assert not base_object.pk in base_result_objects_by_id, (
                "django_polymorphic: result objects do not have unique primary keys - model " + unicode(self.model)
            )

            base_result_objects_by_id[base_object.pk] = base_object

            # this object is not a derived object and already the real instance => store it right away
            if (base_object.polymorphic_ctype_id == self_model_content_type_id):
                results[base_object.pk] = base_object

            else:
                modelclass = base_object.get_real_instance_class()

                # this object is a proxied object of the real instence and already has all the data it needs
                if (ContentType.objects.get_for_model(modelclass).pk == self_model_unproxied_content_type_id):
                    o = modelclass()
                    for k, v in base_object.__dict__.items():
                        o.__dict__[k] = v

                    results[base_object.pk] = o

                # this object is derived and its real instance needs to be retrieved
                # => store it's id into the bin for this model type
                else:
                    idlist_per_model[modelclass].append(base_object.pk)

        # django's automatic ".pk" field does not always work correctly for
        # custom fields in derived objects (unclear yet who to put the blame on).
        # We get different type(o.pk) in this case.
        # We work around this by using the real name of the field directly
        # for accessing the primary key of the the derived objects.
        # We might assume that self.model._meta.pk.name gives us the name of the primary key field,
        # but it doesn't. Therefore we use polymorphic_primary_key_name, which we set up in base.py.
        pk_name = self.model.polymorphic_primary_key_name

        # For each model in "idlist_per_model" request its objects (the real model)
        # from the db and store them in results[].
        # Then we copy the annotate fields from the base objects to the real objects.
        # Then we copy the extra() select fields from the base objects to the real objects.
        # TODO: defer(), only(): support for these would be around here
        for modelclass, idlist in idlist_per_model.items():
            qs = modelclass.base_objects.filter(pk__in=idlist)  # use pk__in instead ####
            qs.dup_select_related(self)  # copy select related configuration to new qs

            for o in qs:
                o_pk = getattr(o, pk_name)

                if self.query.aggregates:
                    for anno_field_name in self.query.aggregates.keys():
                        attr = getattr(base_result_objects_by_id[o_pk], anno_field_name)
                        setattr(o, anno_field_name, attr)

                if self.query.extra_select:
                    for select_field_name in self.query.extra_select.keys():
                        attr = getattr(base_result_objects_by_id[o_pk], select_field_name)
                        setattr(o, select_field_name, attr)

                results[o_pk] = o

            # re-create missing objects:
            for o_pk in idlist:
                if o_pk not in results:
                    o = modelclass()
                    for k, v in base_result_objects_by_id[o_pk].__dict__.items():
                        o.__dict__[k] = v

                    results[o_pk] = o

        # re-create correct order and return result list
        resultlist = [results[ordered_id] for ordered_id in ordered_id_list if ordered_id in results]

        # set polymorphic_annotate_names in all objects (currently just used for debugging/printing)
        if self.query.aggregates:
            annotate_names = self.query.aggregates.keys()  # get annotate field list
            for o in resultlist:
                o.polymorphic_annotate_names = annotate_names

        # set polymorphic_extra_select_names in all objects (currently just used for debugging/printing)
        if self.query.extra_select:
            extra_select_names = self.query.extra_select.keys()  # get extra select field list
            for o in resultlist:
                o.polymorphic_extra_select_names = extra_select_names

        return resultlist