Search code examples
djangoserializationdjango-rest-frameworkormquery-optimization

is there a way for optimize this piece of code?


I have these two model below: models.py file:

class Profile(models.Model):
    """
    User Profile Model
    
    This model represents a user's profile information, including their avatar,
    location, city, address, and geographical coordinates (latitude and longitude).

    Fields:
    - user (OneToOneField): The associated user instance.
    - avatar (ImageField): An optional user avatar (profile picture).
    - location_picture (ImageField): An optional picture of the user's location.
    - city (CharField): An optional field for the user's city.
    - address (CharField): An optional field for the user's address.
    - latitude (DecimalField): An optional field for the latitude of the user's location.
      Valid range: -90.0 to 90.0.
    - longitude (DecimalField): An optional field for the longitude of the user's location.
      Valid range: -180.0 to 180.0.

    """
    user = models.OneToOneField(
        settings.AUTH_USER_MODEL,
        verbose_name=_("User"),
        help_text=_("the profile of a user instance."),
        db_comment=_("the profile of a user instance."),
        on_delete=models.CASCADE
        )
    
    city = models.CharField(
        verbose_name=_("City"),
        help_text=_("'Optional'.this is the city of the user."),
        db_comment=_("'Optional'.this is the city of the user."),
        max_length=255,
        null=True,
        blank=True
        )
    
    address = models.CharField(
        verbose_name=_("Address"),
        help_text=_("'Optional'.this is the address of a user."),
        db_comment=_("'Optional'.this is the address of a user."),
        max_length=255,
        null=True,
        blank=True
    )

    shair_location = models.BooleanField(
        verbose_name=_("Shair Location"),
        default=False,
        help_text=_("'Optional.thie mean you want to shair your location or not!'"),
        db_comment=_("'Optional.this mean you want to shair your location or not!'")
    )

    latitude = models.DecimalField(
        verbose_name=_("Latitude"),
        help_text=_("'Optional'.this is the latitude of a user location."),
        db_comment=_("'Optional'.this is the latitude of a user location."),
        max_digits=9,
        decimal_places=6,
        null=True,
        blank=True,
        validators=[
        MinValueValidator(-90.0),
        MaxValueValidator(90.0)
    ])
    
    longitude = models.DecimalField(
        verbose_name=_("Longitude"),
        help_text=_("'Optional'.this is the longitude of a user location."),
        db_comment=_("'Optional'.this is the longitude of a user location."),
        max_digits=9,
        decimal_places=6,
        null=True,
        blank=True,
        validators=[
        MinValueValidator(-180.0),
        MaxValueValidator(180.0)
    ])

    def __str__(self) -> str:
        return f"user:{self.user}|city:{self.city}"





class Media(models.Model):
    profile = models.ForeignKey(
        Profile,
        verbose_name=_("Profile"),
        help_text=_("the profile can have lot of media(avatar or cover or resume...)"),
        db_comment=_("the profile can have lot of media(avatar or cover or resume...)"),
        on_delete=models.CASCADE,
        related_name="medias"
    )

    media_type = models.IntegerField(
        verbose_name=_("Media Type"),
        choices=MediaType.choices,
        help_text=_("this is the type of the media(avatar:0, resume:1, cover:2, gallery:3)"),
        db_comment=_("this is the type of the media(avatar:0, resume:1, cover:2, gallery:3)"),
        validators=[
            MinValueValidator(0),
            MaxValueValidator(3)
            ]
    )

    image = models.ImageField(
        verbose_name=_("Image"),
        help_text=_("this is the image field"),
        db_comment=_("this is the image field"),
        upload_to="images/%Y/%m/%d/",
        validators=[
            validate_image_size,
        ],
        null=True,
        blank=True
    )

    video = models.FileField(
        verbose_name=_("Video"),
        help_text=_("this is the video field"),
        db_comment=_("this is the video field"),
        upload_to="vidoes/%Y/%m/%d",
        validators=[
            validate_video_size,
        ],
        null=True,
        blank=True
    )

    created_at = models.DateTimeField(
        verbose_name=_("Created At"),
        help_text=_("this is the time the media is created"),
        db_comment=_("this is the time the media is created"),
        auto_now_add=True
    )

and i have this serializer_class below for getting the avatar of a user and it's working but with two extra query for each profile in profiles list endpoint:

serializer.py:

class ProfileSerializer(serializers.ModelSerializer):
    user = UserSerializer()

    avatar = serializers.SerializerMethodField(method_name="get_avatar_url")
    class Meta:
        model = Profile
        fields = ["id", "avatar", "user", "city", "address", "latitude", "longitude"]


    
    def get_avatar_url(self, profile):
        avatar_media = profile.medias.filter(media_type=0).first()
        if avatar_media:
            if avatar_media.image:
                return avatar_media.image
            else:
                return None
        else:
            return None  

media_type == 0 mean avatar

I want to get the image(avatar) of a profile and profiles(profiles endpoint: list of profile) but send two extra query to the database for each profile is there a way to optimize this code or perhaps change it?!


Solution

  • I finally figured out how to optimize this code after a lot of effort First, some changes in the get_serializer_context method in view:

    def get_serializer_context(self):
    if self.request.method == "GET":
        if self.kwargs.get("pk") == None and self.kwargs.get("me") == None:
            avatar_medias = Media.objects.select_related("profile").filter(profile__user=self.request.user, media_type=MediaType.AVATAR)
            return {"request": self.request, "avatars_medias": avatar_medias}
    

    Then we call the avatar medias field in ProfileSerializer:

    class ProfileAdminSerializer(serializers.ModelSerializer):
    user = UserSerializer()
    plan = serializers.SerializerMethodField(method_name="get_plan")
    avatar = serializers.SerializerMethodField(method_name="get_avatar_url")
    
    class Meta:
        model = Profile
        fields = ["id", "avatar", "user", "city", "address", "latitude", "longitude", "plan"]
    
    def get_avatar_url(self, profile):
        avatar_medias = self.context.get("avatars_medias")
        for media in avatar_medias:
            if media.profile == profile:
                if media.image:
                    return media.image.url
        
        else:
            return 
    

    This causes the query of the database, which was one query to the database for each profile, Now we get all the profiles with their avatars with a single query