10

I have the following model:

class Project(models.Model):

    name = models.CharField(max_length=200)

class Task(models.Model):

    name = models.CharField(max_length=200)

    project = models.ForeignKey('Project', on_delete=models.CASCADE,
                                related_name='tasks')

I want to be able to choose the project for a task during creation and forbid to change it once the task was created.

How do I get Task.project to be editable during creation but non editable during updating on a database/model level?

Approaches so far:

  1. The editable=False option

    • This works on the admin/form level, not on the database level
  2. Making a field read-only in django admin

    • This also works on the admin/form level, no on the database level
seblat
  • 141
  • 1
  • 4
  • You can create a database trigger that will raise an error in the database (the implementation depends on which db backend you are using). But the simplest solution is to simply never update that column after you created the object. This works fine for primary keys, for example. – Håken Lid Nov 04 '18 at 17:58
  • It's unclear what specific problem you are trying to solve here. If someone has direct access to modify django application logic, they already own the database. In what scenario would someone be able to change task.project that you want to prevent? – Håken Lid Nov 04 '18 at 18:04
  • Somebody could change task.project by sending an HTTP request on an existing entry and passing a different project within the payload. I know this is very unlikely, but possible. So my approach was to ensure on the database level that it is impossible. I'm not sure if this approach is actually necessary? Maybe I'm overshooting the target? – seblat Nov 05 '18 at 11:49
  • Or maybe the database level is not the right level to solve this on? Then the question would be on which level to solve this? I'm using this model within an API built with django rest framework btw. I can imagine that the serializer might be the right place to implement this? – seblat Nov 05 '18 at 15:47

1 Answers1

10

Not sure about on the database level, but you could use a pre-save signal here and check that instance has a primary key, which will determine if this is the initial save or a modification. If its a modification you can raise an exception if the field has changed.

@receiver(pre_save, sender=Task)
def send_hook_on_roadmap_update(sender, instance, **kwargs):
    try:
        obj = sender.objects.get(pk=instance.pk)
    except sender.DoesNotExist:
        pass  # Initial save -- do nothing
    else:
        if not obj.project == instance.project:  # Field has changed
            raise models.ProtectedError('`project` should not be modified')
Dougyfresh
  • 586
  • 3
  • 15