patternpythondjangoMinor
Storing database references to files/media
Viewed 0 times
storingdatabasefilesreferencesmedia
Problem
This is an extension of this question due to it not covering an important role of my setup that I think needs more attention.
The files are stored on a different server for separation and convenience.
I'm trying to store references to files in my REST API.
I'm using Django for my REST API and I need to find a convenient, practical and quick way to store database references such as images and audio. The features that my implementation supports are as follows:
I would like to keep these features but at the same time keep my code readable and have it make sense.
The Media models look like this:
```
class Media(models.Model):
# Identifiers
user = models.ForeignKey(User, unique=False, related_name = 'media')
folder = models.CharField(max_length=100, blank=True)
uid = models.CharField(max_length=255, unique=True)
# Resource
content_type = models.ForeignKey(ContentType, blank=True, null=True)
object_id = models.PositiveIntegerField(blank=True, null=True)
content = GenericForeignKey('content_type', 'object_id')
# Other
date_created = models.DateTimeField(auto_now=False, auto_now_add=True, blank=True)
class Meta:
unique_together = ('content_type', 'object_id',)
def __str__(self):
return self.uid
class File(models.Model):
name = models.CharField(max_length=100, blank=True)
extension = models.CharField(max_length=100, blank=True)
class Meta:
abstract = True
# Image File
class Image(File):
parent = models.ForeignKey(Media, related_name='images', null=True)
# Dimensions
width = models.CharField(max_length=100, blank=True)
height = models.CharFiel
The files are stored on a different server for separation and convenience.
I'm trying to store references to files in my REST API.
I'm using Django for my REST API and I need to find a convenient, practical and quick way to store database references such as images and audio. The features that my implementation supports are as follows:
- File sets: These make it possible to store several files that are of different size, but represent the same content. (Thumbnails for example)
- Unique UUID for the filesets (also used to check if a user can access the file)
- The models are generic (Meaning they can be applied to any model)
- Some media can be optional
I would like to keep these features but at the same time keep my code readable and have it make sense.
The Media models look like this:
```
class Media(models.Model):
# Identifiers
user = models.ForeignKey(User, unique=False, related_name = 'media')
folder = models.CharField(max_length=100, blank=True)
uid = models.CharField(max_length=255, unique=True)
# Resource
content_type = models.ForeignKey(ContentType, blank=True, null=True)
object_id = models.PositiveIntegerField(blank=True, null=True)
content = GenericForeignKey('content_type', 'object_id')
# Other
date_created = models.DateTimeField(auto_now=False, auto_now_add=True, blank=True)
class Meta:
unique_together = ('content_type', 'object_id',)
def __str__(self):
return self.uid
class File(models.Model):
name = models.CharField(max_length=100, blank=True)
extension = models.CharField(max_length=100, blank=True)
class Meta:
abstract = True
# Image File
class Image(File):
parent = models.ForeignKey(Media, related_name='images', null=True)
# Dimensions
width = models.CharField(max_length=100, blank=True)
height = models.CharFiel
Solution
This part here is strange:
If by design,
Now
That one is really weird as well. Why would you possibly do that?
This already guaranteed you that there can only be a single
Drop either of the relations. In a relational database, you want either a forward, or a reverse reference, but never both. Double linkage in a relational database is in fact not even possible once you start enforcing constraints. Not to mention that it is very easy to create an incoherent state, by inserting sets where the reversed relation points to the wrong row.
I get that you want to express ownership of
Let's just assume that double linkage itself isn't horribly bad, even then
Just drop that
If you want to reliably clear out orphaned media, just write yourself a little garbage collector for that purpose, in that case you can even safely drop that
obj.image.media.all()[0].images.all()If by design,
Post.image only links to media holding images, why would you not write this as a single query instead?obj.image.media.images.all()Now
obj might even be an arbitrary QuerySet of the Post model instead of a single post, and you get the run time down to what you originally wanted. Cutting down on the number of individual database queries is the key here.content = GenericForeignKey('content_type', 'object_id')
class Meta:
unique_together = ('content_type', 'object_id',)That one is really weird as well. Why would you possibly do that?
class PostImage(models.Model):
media = GenericRelation('Media.Media')
resource = models.OneToOneField('Post', null=True, related_name = 'image')This already guaranteed you that there can only be a single
Image holding media object attached to each post, by design.Drop either of the relations. In a relational database, you want either a forward, or a reverse reference, but never both. Double linkage in a relational database is in fact not even possible once you start enforcing constraints. Not to mention that it is very easy to create an incoherent state, by inserting sets where the reversed relation points to the wrong row.
I get that you want to express ownership of
Media in order to be able to collect orphans, but that's not the way to do that.Let's just assume that double linkage itself isn't horribly bad, even then
unique_together = ('content_type', 'object_id',) remains a bad idea. Why would you enforce an 1:0..1 in Media, when you only know that multiplicity constraint for Posts and Media?Just drop that
unique_together, and model the multiplicity solely via PostImage.If you want to reliably clear out orphaned media, just write yourself a little garbage collector for that purpose, in that case you can even safely drop that
GenericForeignKey all together. Finding media which isn't referenced by any other model should be quite easy.Code Snippets
obj.image.media.all()[0].images.all()obj.image.media.images.all()content = GenericForeignKey('content_type', 'object_id')
class Meta:
unique_together = ('content_type', 'object_id',)class PostImage(models.Model):
media = GenericRelation('Media.Media')
resource = models.OneToOneField('Post', null=True, related_name = 'image')Context
StackExchange Code Review Q#144956, answer score: 4
Revisions (0)
No revisions yet.