In Django, How do I deal with concurrent changes to the Images associated with a Post object?
This is a flavour of question that has been asked before, but not quite covering the same issues. I've read through these (question, question, question, and question) but the issue is slightly different.
I have a blog post model (pseudocode for speed), which contains title, abstract and body, and associated Images.
class Post(models.Model):
title = CharField
abstract = TextField
body = TextField
class Image(models.Model):
post = ForeignKey(Post)
imagefile = ImageField
Now, what I want to add is the ability to store histories of the changes to this Post
model. I've thought of two possibilities for this:
Possibility 1
class PostHistory(models.Model):
post = ForeignKey(Post)
title_delta = TextField
abstract_delta = TextField
body_delta = TextField
However this has the issue that it is storing deltas for no changes (for example when title
does not change and there is only a delta for the body
field. That said, when more than one field changes, it fits that '1 revision == 1 complete revision'.
Possibility 2
class PostRevision(models.Model):
post = ForeignKey(Post)
field = CharField #Field name
delta = TextField
Through two different approaches, this successfully gives me a history of diffs for the field, which I would generate using diff-match-patch (slightly more performant than the inbuilt difflib). The two issues I now have are related to the generation of master objects (i.e. the top revision in the chain).
The question being asked is: How do I deal with concurrent changes to the Images associated with a Post object? These would be changed via references within the body
field of the Post
model (this is a Markdown formatted text field which is then edited on POST
of the form to add in the URL references for the image field). Is the best way to deal with this to use an M2M field on the revision, and on the Post
object, allowing the images to be always stored with the PostRevision
object?
django-simple-history stores Django model state on every create/update/delete. This app supports the following combinations of Django and Python: Django. Python.
A model is the single, definitive source of information about your data. It contains the essential fields and behaviors of the data you're storing. Generally, each model maps to a single database table. The basics: Each model is a Python class that subclasses django.db.models.Model .
By default, Django model relationships are established on the primary key of a model which in itself defaults to a model's id field. For example, the field menu = models. ForeignKey(Menu) stores the id from a Menu instance as the relationship reference.
I agree with @rickard-zachrisson that you should stick to approach #1. I'd make a few subtle changes though (pseudo code btw):
class AbstractPost(models.Model):
title = CharField
abstract = TextField
body = TextField
class Meta:
abstract = True
class Post(AbstractPost):
def save(self):
post = super(Post, self).save()
PostHistory.objects.create(
post=post,
title=post.title,
abstract=post.abstract,
body=post.body,
)
class PostHistory(AbstractPost):
post = ForeignKey(Post)
class Meta:
ordering = ['-pk']
class Image(models.Model):
post = ForeignKey(Post)
imagefile = ImageField
Your latest version will always be in Post
and your change history is in pk
order in PostHistory
which is easy to diff against for changes. I'd duplicate the data because storage is cheap and storing deltas is a pita. If you have multiple edits or want to compare the current version to the original version then deltas are basically useless. Any model changes in AbstractPost are reflected in both Post
and PostHistory
.
Image
is keyed to Post so things stay tidy. You can optionally clean up images in your Post.save() function but I'd probably opt for a post_save signal to keep the code cleaner.
I think you should stick with option 1.
An idea would be to have an automated revision system. Here is how I would do and mind some syntax errors, im typing out of my head
class A(models.Model):
field1 = ...
field2 = ...
def save():
if bla_bla_updated:
A_revisions.objects.create(
field1=self.fields1, field2=self.fields2,
a=self)
super(A, self).save()
class A_revision(models.Model):
field1 = ...
field2 = ...
a = models.ForeignKey(A)
revision = models.IntegerField()
def save():
self.revision = (A_revision.objects.get(a=self.a)
.order_by('id').revision) + 1
super(A_revision, self).save()
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With