How to create Create for ManyToMany relation with through? - python

I have models like:
class Playlist(models.Model):
key = models.CharField(max_length=255, blank=True, unique=True)
user = models.ForeignKey(User)
title = models.CharField(max_length=200)
pub_date = models.DateTimeField(auto_now_add=True)
videos = models.ManyToManyField(Video, through='PlaylistVideo')
class PlaylistVideo(models.Model):
playlist = models.ForeignKey(Playlist)
video = models.ForeignKey(Video)
position = models.IntegerField()
class Video(models.Model):
title = models.CharField(max_length=255,blank=True)
description = models.TextField(blank=True)
thumb = models.URLField(blank=True)
duration = models.IntegerField(default=0)
I want to write POST (create) for Playlist an API. Not sure how to handle videos field.
How to send values for videos field?
Django 1.8 and django-rest-framework
Thanks

From DRF documentation :
By default, relational fields that target a ManyToManyField with a through model specified are set to read-only.
So you most definitely CAN NOT create Video instances when creating a Playlist.
Now, considering you already have Video objects, and you want to create a Playlist which contains some of those videos, there are two ways to go about this.
You create your m2m relations at the same time as you create the Playlist :
class PlaylistSerializer(ModelSerializer):
videos = PrimaryKeyRelatedField(many=True)
class Meta:
model = Playlist
You only create the Playlist on POST, without any videos, and then you create another endpoint (e.g. playlist/<playlist_id>/videos/) where you add/remove the videos that you want.
Both have pros and cons, but my opinion is that you should first design you API, thinking also about how you will continue editing that Playlist.
Do you add videos one by one ? Do you add them in bulk ?
How do you remove a video ? Can that be done in bulk ?
Once you've got the right answer to those questions, implementation should be fairly simple.
Good luck!

Related

Django efficient Queryset with Foreign Key Models

I'm trying to find the most efficient way (as less db queries as possible) for the following model structure.
In my template I then want to pass all the data from all 3 models because I would have to show the post data as well as looping through the comments to create a comments list and display all the attachments for the different comments.
class Post(BaseModel):
user = models.ForeignKey('User', blank=True, null=True,
title = models.CharField(max_length=128)
content = models.TextField()
class Comment(BaseModel):
post = models.ForeignKey('Post', on_delete=models.CASCADE)
user = models.ForeignKey('User', on_delete=models.SET_NULL)
text = models.TextField()
class CommentAttachment(BaseModel):
comment = models.ForeignKey('Comment', on_delete=models.CASCADE)
name = models.CharField(max_length=128)
Should I fetch all data from CommentAttachment direction (meaning fetching all CommentAttachments where comment__post__id is the post id and then get all other data with select_related) or is there another way to start from the Post Model?
You can use prefetch_related or select_related in your query:
posts = Post.objects.filter(user=some_user).prefetch_related(
'comment_set', 'comment_set__commentattachment_set'
)
For example, after making a query as mentioned, the following command may retrieve all the comments for the first post in the queryset without making a SQL query:
posts.first().comment_set.all()

Storing several informations in one variable in Django Models

class Cities(models.Model):
city_main_image = models.FileField()
city_name = models.CharField(max_length=200)
city_info = models.CharField(max_length=1000)
city_images = models.FileField()
In my models.py I have Cities class and I want to upload several images for this class variable, to be clearly for city_images variable how can I do this? Are there any way to do this or not?
Few notes about your code before answering your question.
1) Stick to singular model names, City rather than Cities.
2) Don't repeat the model name in every field, so you get "main_image",
"name", "info", "images".
3) Use ImageField for images rather than FileField.
4) No need to have 2 fields for main_image and images. You can add an extra field to make the image a main image or not.
Now, to answer your question, you need to read about relations in an SQL database.
To use relations between your models with django's ORM, look at https://docs.djangoproject.com/en/2.0/ref/models/fields/#django.db.models.ForeignKey
You have 2 options: ForeignKey or ManyToManyField. Stick with the ForeignKey as you don't need a many to many relation.
So you'll have something like the following:
class City(models.Model):
...
class CityImage(models.Model):
image = models.ImageField(...)
city = models.ForeignKey(City) # you might need to add on_delete parameter depending on your django version.
or
class CityImage(models.Model):
image = models.ImageField(...)
class City(models.Model):
...
images = models.ManyToManyField(CityImage)
class Cities(models.Model):
city_main_image = models.FileField()
city_name = models.CharField(max_length=200)
city_info = models.CharField(max_length=1000)
class CityImages(models.Model):
city_id = models.ForeignKey(Cities)
city_images = models.FileField()
Now each of your city in Cities can have one or more images in another model called CityImages. If we talk in terms of tables then the primary key for a row in table Cities would be associated to one or more rows in table city_images. I will strongly suggest you to go through official introductory tutorial of django. Also I personally find this tutorial very helpful for beginners. Just in case it helps.
One way you could do this is to make a CityImage model, which would allow you to make a ForeignKey to the City model, with a related_name=images for the reverse lookup.
class City(models.Model):
name = models.CharField(max_length=200)
info = models.CharField(max_length=1000)
#property
def main_image(self):
try:
return self.images.get(primary=True)
except CityImage.DoesNotExist:
pass
except CityImage.MultipleObjectsReturned:
# Handle this case, the application should ensure only one is set to `True` at a time...
class CityImage(models.Model):
city = models.ForeignKey(City, related_name='images')
primary = models.BooleanField(default=False)
image = models.FileField()

django ModelForm foreign key - text widget add if not exist

I am just starting out with Django and have the following:
models.py:
class Song(models.Model):
name = models.CharField(max_length=200, blank=False, null=False)
artist = models.ForeignKey(Artist, blank=False, null=False)
class Artist(models.Model):
name = models.CharField(max_length=200, unique=True)
Now I have a model form for Song but currently have no ability to add artists that don't already exist (rendered as a dropdown). It would be nice to allow users to add artists on the fly but haven't been able to find a way to get that working. I saw lots of answers relating to replicating the admin "add another..." but kept running into roadblocks and outdated information.
What I tried:
replicating the "add another" from the admin console
Creating as a regular form - but abandoned it because ModelForms gives me a lot for free
Started researching on formsets but got stuck there, could not find a working example
Is there a way to easily add another artist from the song form? I don't mind rendering a new textbox below the artist selection where the user can add a new artist but I don't know how to do that with ModelForms and then add the artist into the database before saving.
Any advice would be much appreciated!
It would help to see what you are using to create your forms. I assume you are using a ModelFrom. If you are using jQuery I think you could use the following in your forms.py to capture a new artist. However, if you are using jQuery I would save individual forms as templates and display them as necessary depending on a button or link event for a new artist.
forms.py
class SongForm (forms.ModelForm):
new_artist_name = forms.CharField()
class Meta:
model = Song
def save(self, commit=True):
# do something with self.cleaned_data['new_artist']
new_artist = Artists.objects.filter('new_artist_name')
if new_artist.exists():
# Save song to artist.
else:
# Create and save new artist and save song to the
# new artist.
return super(SongForm, self).save(commit=commit)

Python Django how to retrieve specific objects

I've got this Post model at the moment:
class Post(models.Model):
title = models.CharField(max_length = 140)
body = models.TextField()
date = models.DateTimeField()
def __unicode__(self):
return self.title
If I've got different parts of a website (or a forum rather) that contain different posts, e.g. Discussion about basketball, and Discussion about football, if I wanted to return just posts concerning basketball or just posts concerning football, is the easiest way to just make a specific basketball_post model/football_post model or is there a more efficient way? Should I perhaps be storing the values differently?
Thanks
Django has a really good tutorial. It is about making a Poll app. In the first chapter the thing you want is discussed. It is about a Question that can have multiple Choices.:
class Question(models.Model):
question_text = models.CharField(max_length=200)
pub_date = models.DateTimeField('date published')
class Choice(models.Model):
question = models.ForeignKey(Question)
choice_text = models.CharField(max_length=200)
votes = models.IntegerField(default=0)
The foreignKey creates a relation between two models. The same can be done for a blog:
class Category(models.Model):
title = models.CharField(max_length=200)
class Post(models.Model):
category = models.ForeignKey(Category) # This is the important part.
title = models.CharField(max_length=200)
body = models.TextField()
date = models.DateTimeField()
def __unicode__(self):
return self.title
The ForeignKey relation lets you do really nice things:
basketball_posts = Post.objects.filter(category_title='Basketball')
But before we all tell you how it is done, I really recommend to do the tutorial. It introduces you to all important Django concepts: https://docs.djangoproject.com/en/1.7/intro/tutorial01/
Update
If you have a fixed set of categories that are not likely to change, than you can hardcode them and use field choices:
class Post(models.Model):
FOOTBALL = 'F' # Variable name and db_value
CRICKET = 'C'
INTRODUCTION = 'I'
CATEGORY_CHOICES = (
(FOOTBALL, 'Soccer'), # Variable name and display value
(CRICKET, 'Cricket'),
(INTRODUCTION, 'Hello my name is'),
)
category = models.CharField(max_length=1,
choices=CATEGORY_CHOICES,
default=INTRODUCTION)
...
https://docs.djangoproject.com/en/dev/ref/models/fields/#choices
One of the advantages of this 'choice machinery' over a CharField without pre defined choices is that you are sure what values end up in your database. This lets you query them, without worrying if your data is sane:
Post.objects.filter(category=Post.CRICKET)
Use the extra table if you need the freedom to create new categories in the future. Use field choices if you don't want (or need) that freedom.
I would suggest to just add a field which makes the post relevant to that certain topic:
class Post(models.Model):
title = models.CharField(max_length = 140)
body = models.TextField()
date = models.DateTimeField()
type = models.CharField(max_length=20) #<--- new field: e.g 'basketball','hockey'..
def __unicode__(self):
return self.title
example query:
#basketball posts
qs = Post.objects.filter(type__icontains="basketball")
then you dont need to have multiple models which also would be redundant.
Assuming all of the posts are in the same format, you could add another field to your model like "type". Different discussion forums could send a different values for that field when the post is added.
type = models.CharField(max_length=140, choices=['Football', 'Basketball', 'Baseball'])
Storing this would make it easy to filter which posts are which.
Post.objects.filter(type = 'Football')
Assuming that one post can be about only one sport, the better approach would be to have a foreign key relation between a model that stores data about a post with another model that stores the data about sports.
Something like this
class Sport(models.Model):
name = models.CharField(max_length = 200)
description = models.TextField()
def __unicode__(self):
return self.name
class Post(models.Model):
title = models.CharField(max_length = 140)
body = models.TextField()
date = models.DateTimeField()
sport = models.ForeignKey(Sport)
def __unicode__(self):
return self.title
This gives you the advantage of isolating the 'Sport' and the 'Post' models.You can add as many sports as you want, without any posts referring to it.
One more advantage is that you can add relevant information to the relevant models.
Eg:Suppose you want to add the information about "how many players are there in a team for sport x?". You can easily achieve this by adding a field "number_of_players" in the 'Sport' model without affecting the 'Post' model.
If you had to do this in one model, 'Post', then it would create lot of issues in terms of data consistency and other undesirable things.
Also, the query will look something like this:
posts = Post.objects.filter(sport__name = "Basketball")
PS:If your requirement is that a post can be tagged to multiple sports, then you can use ManyToMany field instead of a simple foreign key.
https://docs.djangoproject.com/en/dev/topics/db/examples/many_to_many/
You could assign your posts tags or category, and filter on those.
If you use the model approach what happens when you add more sports? You'll need manually add the sports in your code, using a tags or category approach allows you to handle it in the db, and would then allow you to filter on the tags/categories in your system

Django Adding a ManyToManyField to ModelForm

Sorry that this is like the thousandth question for this issue but I still can't see a light at the end of the tunnel.
Lets say I have two models:
class Video(models.Model):
title = models.CharField(u"Titel",max_length=200)
slug = AutoSlugField(populate_from='title',unique=True)
date = models.DateField("Datum")
description = models.TextField(u"Beschreibung")
user = models.OneToOneField(User, blank=True, null=True)
class Channel(models.Model):
name = models.CharField(u"Name",max_length=30)
slug = AutoSlugField(populate_from='name',unique=True)
videos = models.ManyToManyField('videoportal.Video',related_name="contained_videos",blank=True,null=True)
created = models.DateTimeField(auto_now_add=True)
modified = models.DateTimeField(auto_now=True)
As you see I want to have a channel with video(s) in it. So if I ad a video using a ModelForm like this
class VideoForm(ModelForm):
class Meta:
model = Video
the form I get will not contain a input field to select a channel (of course not). So how can I do this? How can I have a input field in my form to select one channel with a drop down?
Thanks,
Philip
If a video only belongs in one channel just give your Video model a ForeignKey to your Channel model. If it should belong to more than one channel I'd use a ManyToManyField in the Video model, as already suggested.
I think this would fit the idea of uploading videos and adding it to a channel far better than doing it the other way around.
Try putting the ManyToMany field in the Video model and omit it from the Channel model:
class Video(model.Model):
...
channels = model.ManyToManyField('videoportal.Channel', related_name='videos')
...
If you want a simple dropdown to select a single channel, why is it a many-to-many realationship between videos and channels?
Use a custom form instead of Django ModelForm.
probably something like this,
class VideoForm(forms.Form):
title = forms.CharField()
description = forms.TextField()
channel = forms.ModelChoiceField(queryset= Channel.objects.all(), empty_label=None)
do your validation in a view. Use Model save() method to save information contained in your POSTed form.

Categories