What is the best practice to choose one item from a m2m relationship?
Lets say I've got an album of photos:
class Photo(models.Model):
img = models.FileField()
class Album(models.Model):
photos = models.ManyToManyField("Photo")
But now I also want to pick one photo as a cover. I could use a Foreign Key in Album to one Photo, but then I'd always need to check whether this photo is actually in the photos of that album. Is there a better way?
Sorry for the basic question I just somehow can't find the right words to google it.
Thanks,
Michael
You can make a custom through=… model [Django-doc] with a boolean cover that is set to True in case it is the cover item:
from django.db.models import Q, UniqueConstraint
class Photo(models.Model):
img = models.FileField()
class Album(models.Model):
photos = models.ManyToManyField(Photo, through='AlbumPhoto')
class AlbumPhoto(models.Model):
photo = models.ForeignKey(Photo, on_delete=models.CASCADE)
album = models.ForeignKey(Album, on_delete=models.CASCADE)
cover = models.BooleanField(default=False)
class Meta:
constraints = [
UniqueConstraint(fields=['photo', 'album'], name='no_photo_multiple_times'),
UniqueConstraint(fields=['album'], condition=Q(cover=True), name='one_cover_per_album'),
]
The first constraint guarantees that you can not add the same Photo multiple times to the same Album, whereas the second condition gurantees that each Album has at most one Photo for which cover=True.
Related
Imagine there are three models named Movie, Actor, and Participation.
class Movie(models.Model):
identifier = models.CharField()
class Actor(models.Model):
name = models.CharField()
class Participation(models.Model):
movie_identifier = models.CharField()
actor = models.ForgeinKey(Actor, on_delete=models.CASCADE)
Let's assume that I can't use ForgeinKey for the movie in the Participation model.
how can I retrieve all the participation records of a movie with only one query?
Here is the solution if I had a foreign key for the movie in the participation table:
qs = Movie.objects.filter(identifier="an_identiier").prefetch_related("participations_set")
How can I do this without having a Movie foreign key in the Participation model?
Thanks!
One of the most important things when designing a database (hence when designing your models) is database normalization [Wikipedia].
You talk about Participation being related to multiple models like Movie, Series, Episode, etc. this means that Movie, Series, Episode all can be said to have something in common or they can be said to be a specialization of another entity let us say Participatable for the lack of a better word, or we can say Participatable is a generalization of Movie, Series, Episode, etc.
How do we model these? Well we will just have an extra model that our other models will have a OneToOneField with:
class Participatable(models.Model):
# Any common fields here
MOVIE = 'M'
SERIES = 'S'
TYPE_CHOICES = [
(MOVIE, 'Movie'),
(SERIES, 'Series'),
]
subject = models.CharField(max_length=1, choices=TYPE_CHOICES)
class Movie(models.Model):
# uncommon fields
participatable = models.OneToOneField(
Participatable,
on_delete=models.CASCADE,
related_name='movie',
)
class Series(models.Model):
# uncommon fields
participatable = models.OneToOneField(
Participatable,
on_delete=models.CASCADE,
related_name='series',
)
class Participation(models.Model):
participatable = models.ForgeinKey(Participatable, on_delete=models.CASCADE)
actor = models.ForgeinKey(Actor, on_delete=models.CASCADE)
Other than this solution which I find is the best for such modelling you can go with using the content-types framework which will essentially do what you do currently. That is it will use a field that stores the related id and also a foreign key that points to an entry in a table that will simply describe which table this id is for.
Imagine that you have a product which has a cover URL (cover is presented to customers on the landing page) and a list of image URLs (these images show different sides of the product)
problem: how to separate cover from other images?
class Image(models.Model):
url = models.URLField()
class Product(models.Model):
cover = ...?
album_images = ...?
Using ForeignKey in the Image model is not an option because when using product.image_set.all() you will get all images including the cover.
Any suggestions will be appreciated.
One common way would be:
class Product(models.Model):
...
class Image(models.Model):
product = models.ForeignKey(Product, on_delete=models.CASCADE, related_name="images")
URL = models.URLField()
is_cover = models.BooleanField()
Because of the "related_name" you can access the images from the product instance.
prod = Product.objects.get(pk=123)
cover = [x for x in prod.images if x.is_cover]
To find the cover image best to add a method to the Product so you can call something like prod.get_cover() or so.
I have two models:
class Photo(models.Model):
# fields
class PhotoTags(models.Model):
photo = models.ForeignKey(Photo, related_name="tags")
tag_name = models.Charfield()
is_disabled = models.BooleanField(default=False)
What I'm trying to achieve is to get photos where tags are all with is_disabled = True.
Is it possible to achieve it with a query, or do I have to loop photos and for each one check all tags?
EDIT
I tried with
Photos.objects.filter(tags__is_disabled=True)
but it returns photos with at least one tag that is disabled
Thank you
Yo can use Photo.objects.filter(tags__is_disabled=True)
I have Three models
class Song(models.Model):
title = models.CharField(max_length=100)
bandname = models.ManyToManyField(Band)
albumname = models.ManyToManyField(Album)
class Band(models.Model):
title = models.CharField(max_length=100)
class Album(models.Model):
title = models.CharField(max_length=100)
Now I wants to list all albums for a particular band.
I've tried this way, in view.py
band = Band.objects.get(id=no)
song_list = band.song_set.all()
album = [i.bandname for i in song_list]
It's not working. Please help me.
It seems what you're actually trying to get is a list of all the albums that contain songs by a particular band, either exclusively or in duet. Here's how you could get that using your existing models:
band = Band.objects.get(id=target_band_id)
song_list = Song.objects.filter(bandname=band)
album_list = Album.objects.filter(song__in=song_list).distinct()
Check "Reverse m2m queries" within the Django Many to Many docs.
I would also suggest re-naming bandname and albumname to simply band and album because they are referring to band or album model instances, not the actual names of the band or album. That would get confusing later on if you wanted to add a name field to either the Band or Album models.
how to add extra information to the tag system or access through model in django-taggit?
My model 'Post' has an image and a TaggableManager, so that each Post (or image) can have multiple tags on it, and users can search all posts by their tags. Now, I need to specify the position of the tagged tag on each image (similar concept that users tag a friend on a specific location on a photo).
I think I need to add extra information to the through model because the coordinates belong only to the relation of a post and tag, not to the tag itself. Here is the model.py (simplified):
class TaggedPost(taggit.models.TaggedItemBase):
content_object = models.ForeignKey('Post')
x = models.IntegerField()
y = models.IntegerField()
class Post(models.Model):
image = models.ImageField(upload_to='p/%Y/%m/%d/')
tags = TaggableManager(through=TaggedPost)
However, I cannot access the through model via the API of TaggableManager. Is it possible to read the data? I don't want to build another relation or mapping table.
Thanks for any suggestion.
I asked in the Google forum of taggit but no replies. Here is my solution at the end (might not be the best)
I build my own GenericTaggedItemBase like this:
class GenericPostTaggedItemBase(ItemBase):
content_object = models.ForeignKey('Post', related_name='%(class)s')
x = models.IntegerField(null=True, default=None, blank=True)
y = models.IntegerField(null=True, default=None, blank=True)
class Meta:
abstract = True
Then similar to TaggedItem in Taggit:
class PostTaggedItem(GenericPostTaggedItemBase, TaggedItemBase):
class Meta:
verbose_name = _("Tagged Item")
verbose_name_plural = _("Tagged Items")
In my Post model:
class Post(models.Model):
# whatever fields go here
tags = TaggableManager(through=PostTaggedItem)
To assess the data:
Post post = Post.objects.get(pk=1)
models = post.posttaggeditem.all()
for item in models:
# here is item.x and item.y
The key here is the related_name in GenericPostTaggedItemBase. It must be %(class)s or similar (see the source in taggit). So that the generic base can be used with other type of tags, if you have. Also, this is kind of 'recommended' for abstract class (don't use fixed related_name for abstract class). For details, search about related_name and %(class). I will skip this here.
If you have any ideas or suggestions, please let me know.