0

I want to create an immutable copy of the model instance, such that the user be able to access the details of the model, including its attributes, but not the save and the delete methods. The use case is that there are two repos accessing the django model, where one is supposed to have a writable access to the model, while another should only have a readable access to it.

I have been researching ways of doing this. One way, I could think is the readable repo gets the model instance with a wrapper, which is a class containing the model instance as a private variable.

class ModelA(models.Model):
    field1=models.CharField(max_length=11)
class ModelWrapper:
    def __init__(self,instance):
        self.__instance=instance
    def __getattr__(self,name):
        self.__instance.__getattr__(name)

The obvious problem with this approach is that the user can access the instance from the wrapper instance:

# model_wrapper is the wrapper created around the instance. Then
# model_wrapper._ModelWrapper__instance refers to the ModelA instance. Thus
instance = model_wrapper._ModelWrapper__instance
instance.field2="changed"
instance.save()

Thus, he would be able to update the value. Is there a way to restrict this behaviour?

phoenix97
  • 179
  • 3
  • 11
  • In addition to the django model answers, consider using the DB's `GRANT` statements to ensure no accidental access occurs, https://stackoverflow.com/a/762649/1464664 – Michael Lindsay Jan 19 '21 at 11:34

2 Answers2

1

Try overriding the models save and delete in webapp where you want to restrict that:

class ModelA(models.Model):
    field1=models.CharField(max_length=11)
    
    def save(self, *args, **kwargs):
        return # Or raise an exception if needed
    
    def delete(self, *args, **kwargs):
        return # Or raise an exception if needed

If you are using update or delete on a queryset you might also need a pre_save and pre_delete signal:

from django.db.models.signals import pre_delete

@receiver(pre_delete, sender=ModelA)
def pre_delete_handler(sender, instance, *args, **kwargs):
        raise Exception('Cannot delete')

Edit: Looks like querysets don't send the pre_save/post_save signal so that cannot be used there, the delete signals are emitted though.

Abdul Aziz Barkat
  • 19,475
  • 3
  • 20
  • 33
  • Yes. This method exactly works. Though, I was thinking, if I add the signal to the wrapper. So, the pre_delete signals are overriden only when they are called from the wrapper, it is not working. Is there a way to setup the receiver from the init method of the wrapper class? – phoenix97 Jan 19 '21 at 10:52
  • @phoenix97 Signals are emitted when a model is updated / deleted I don't know how you would only receive a signal if the model is inside a wrapper unless you customize all of that. Can you add a variable in your settings.py to indicate whether writing is allowed on a model according to the repo / indicate which repo it is? If so I can submit another answer which may work for you. – Abdul Aziz Barkat Jan 19 '21 at 11:01
1
class ModelA(models.Model):
    field1=models.CharField(max_length=11)

class ModelWrapper:
    def __init__(self, instance):
        self.__instance=instance

        # Delete unwanted attributes
        delattr(self.__instance, 'save') 
        delattr(self.__instance, 'delete')
    def __getattr__(self,name):
        self.__instance.__getattr__(name)
Safwan Samsudeen
  • 1,645
  • 1
  • 10
  • 25