danirus / django-comments-xtd

A pluggable Django comments application with thread support, follow-up notifications, mail confirmation, like/dislike flags, moderation, a ReactJS plugin and Bootstrap 5.3.
https://django-comments-xtd.readthedocs.io
BSD 2-Clause "Simplified" License
595 stars 158 forks source link

(experimental) Allow using proxy models and change the pk with them. #345

Open PetrDlouhy opened 2 years ago

PetrDlouhy commented 2 years ago

I would like to use UUIDs instead of IDs as mentioned in #179. Since I don't have UUID set as primary key in my app and it would be very hard and dangerous to change migrate it, I would like to change the PK somehow for django-comments-xtd.

I was able to achieve thet with few changes to django-comments-xtd (the for_concrete_model parameters can be made configurable from settings) and quite extensive hacking of the Proxy model and related objects:

from django_comments_xtd.models import XtdComment
from django.contrib.contenttypes.models import ContentType

from ..assets.models import Asset

class UUIDAsset(Asset):

    def _get_pk_val(self):
        return self.version_uuid

    class Meta:
        proxy = True

def uuid_get(*args, **kwargs):
    if 'pk' in kwargs:
        return UUIDAsset.objects._get(*args, version_uuid=kwargs['pk'])
    return UUIDAsset.objects._get(*args, **kwargs)

@property
def uuid_content_object(comment):
    if comment.content_type.model == 'uuidasset':
        asset_uuid = comment.object_pk
        return UUIDAsset.objects.get(version_uuid=asset_uuid)
    return comment._content_object

def uuid_get_object_for_this_type(content_type, **kwargs):
    if content_type.model == 'uuidasset':
        asset_uuid = kwargs['pk']
        return UUIDAsset.objects.get(version_uuid=asset_uuid)
    return content_type._get_object_for_this_type(**kwargs)

UUIDAsset.version_uuid.primary_key = True
UUIDAsset.objects._get = UUIDAsset.objects.get
UUIDAsset.objects.get = uuid_get
UUIDAsset._meta.pk = UUIDAsset._meta.get_field('asset_uuid')

XtdComment._content_object = XtdComment.content_object
XtdComment.content_object = uuid_content_object

ContentType._get_object_for_this_type = ContentType.get_object_for_this_type
ContentType.get_object_for_this_type = uuid_get_object_for_this_type

I am posting this here for further discussion.

I was even trying to make it that the user could set id_field for the model in COMMENTS_XTD_APP_MODEL_OPTIONS, which would be much easier the user to use, but also would require some of the hacking be inside of django-contribs-xtd (unfortunately there is no support for such things in django.contrib.contenttypes).

PetrDlouhy commented 2 years ago

I have updated the code, so that it uses COMMENTS_FOR_CONCRETE_MODEL setting to pass to ContentType framework. I made similar Pull request to django-contrib-comments, which is also needed to run hacks: https://github.com/django/django-contrib-comments/pull/177

Updated hack code is here:

from django.contrib.contenttypes.models import ContentType
from django.db import models
from django_comments_xtd.models import XtdComment

from ..assets.models import Asset

class UUIDAssetQuerySet(models.query.QuerySet):
    def get(self, **kwargs):
        if 'pk' in kwargs:
            return super().get(asset_uuid=kwargs['pk'])
        return super().get(**kwargs)

class UUIDAsset(Asset):
    objects = models.Manager.from_queryset(UUIDAssetQuerySet)()

    def _get_pk_val(self):
        return self.asset_uuid

    class Meta:
        proxy = True
        app_label = 'assets'

def uuid_content_object(comment):
    if comment.content_type.model == 'uuidasset':
        asset_uuid = comment.object_pk
        return UUIDAsset.objects.get(asset_uuid=asset_uuid)
    return comment._content_object

_get_object_for_this_type = ContentType.get_object_for_this_type

def uuid_get_object_for_this_type(self: ContentType, **kwargs) -> models.Model:
    if self.model == 'uuidasset':
        asset_uuid = kwargs['pk']
        return UUIDAsset.objects.get(asset_uuid=asset_uuid)
    return _get_object_for_this_type(self, **kwargs)

UUIDAsset.asset_uuid.primary_key = True
UUIDAsset._meta.pk = UUIDAsset._meta.get_field('asset_uuid')

XtdComment._content_object_tmp = XtdComment.content_object
XtdComment.content_object = uuid_content_object

ContentType.get_object_for_this_type = uuid_get_object_for_this_type
danirus commented 2 years ago

Thanks @PetrDlouhy, the PR code looks good (thanks for the additional fixes). On the other hand the example code seems a bit like a hack, which is a pity given the achievement. To display the full picture, could you please add the part of the code that belongs to the Asset model? With a little bit of make-up it could be used as an example to add to the use cases page of the docs.