Django unique_together on primary key - django

Currently I have three models:
class Tutorial(models.Model):
title = models.CharField(max_length=100)
description = models.TextField()
videos = models.ManyToManyField('TutorialVideo', through='TutorialVideoThrough', blank=True)
class TutorialVideo(models.Model):
name = models.CharField(max_length=100)
video = S3DirectField(dest='tutorial_vids')
length = models.CharField("Length (hh:mm:ss)",max_length=100)
tutorials = models.ManyToManyField(Tutorial, through='TutorialVideoThrough', blank=True)
class TutorialVideoThrough(models.Model):
tutorial = models.ForeignKey(Tutorial, related_name='tutorial_video_through', blank=True, null=True)
video = models.ForeignKey(TutorialVideo, related_name='video_tutorial_through', blank=True, null=True)
order = models.IntegerField()
A Tutorial can have many TutorialVideos through TutorialVideoThrough. On the TutorialVideoThrough, I have an order field to decide what order to show the videos in. Is there a way to validate that there are no duplicate order integers? For example, if I am linking two TutorialVideos to a Tutorial through TutorialVideoThrough, and I put the first one as order 1, then I shouldn't be able to give the second video as order 1.
I tried using unique_together = ('id', 'order') on TutorialVideoThrough, but it didn't work.

primary_key is always unique, so your unique_together pair also will be always unique.
If you must ensure that there are no videos with same order, you must first answer question: regarding to what?
If you want to have your uniqueness in list of TutorialVideos in Tutorials, your unique_together should be:
unique_together = (('tutorial', 'order'),)
If you want uniqueness in list of Tutorials in TutorialVideos, your unique_together should be:
unique_together = (('video', 'order'),)
But setting order as unique is not good idea, you will have some issues when trying to reorder your fields.

If you want each order to be unique for each tutorial, then you want
unique_together = (
('tutorial', 'order'),
)
You probably want each video to appear only once in each tutorial. If that's the case, add ('tutorial', 'video') to unique_together.
unique_together = (
('tutorial', 'order'),
('tutorial', 'video'),
)
Note that you should only define the many to many relation on one side of the model. If you have
class Tutorial(models.Model):
videos = models.ManyToManyField('TutorialVideo', through='TutorialVideoThrough', blank=True, related_name='tutorials')
then you should remove the tutorial field from the TutorialVideo model.

Related

how to build query with several manyTomany relationships - Django

I really don't understand all the ways to build the right query.
I have the following models in the code i'm working on. I can't change models.
models/FollowUp:
class FollowUp(BaseModel):
name = models.CharField(max_length=256)
questions = models.ManyToManyField(Question, blank=True, )
models/Survey:
class Survey(BaseModel):
name = models.CharField(max_length=256)
followup = models.ManyToManyField(
FollowUp, blank=True, help_text='questionnaires')
user = models.ManyToManyField(User, blank=True, through='SurveyStatus')
models/SurveyStatus:
class SurveyStatus(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE)
survey = models.ForeignKey(Survey, on_delete=models.CASCADE)
survey_status = models.CharField(max_length=10,
blank=True,
null=True,
choices=STATUS_SURVEY_CHOICES,
)
models/UserSurvey:
class UserSurvey(BaseModel):
user = models.ForeignKey(User, null=True, blank=True,
on_delete=models.DO_NOTHING)
followups = models.ManyToManyField(FollowUp, blank=True)
surveys = models.ManyToManyField(Survey, blank=True)
questions = models.ManyToManyField(Question, blank=True)
#classmethod
def create(cls, user_id):
user = User.objects.filter(pk=user_id).first()
cu_quest = cls(user=user)
cu_quest.save()
cu_quest._get_all_active_surveys
cu_quest._get_all_followups()
cu_quest._get_all_questions()
return cu_quest
def _get_all_questions(self):
[[self.questions.add(ques) for ques in qstnr.questions.all()]
for qstnr in self.followups.all()]
return
def _get_all_followups(self):
queryset = FollowUp.objects.filter(survey__user=self.user).filter(survey__user__surveystatus_survey_status='active')
# queryset = self._get_all_active_surveys()
[self.followups.add(quest) for quest in queryset]
return
#property
def _get_all_active_surveys(self):
queryset = Survey.objects.filter(user=self.user,
surveystatus__survey_status='active')
[self.surveys.add(quest) for quest in queryset]
return
Now my questions:
my view sends to the create of the UserSurvey model in order to create a questionary.
I need to get all the questions of the followup of the surveys with a survey_status = 'active' for the user (the one who clicks on a button)...
I tried several things:
I wrote the _get_all_active_surveys() function and there I get all the surveys that are with a survey_status = 'active' and then the _get_all_followups() function needs to call it to use the result to build its own one. I have an issue telling me that
a list is not a callable object.
I tried to write directly the right query in _get_all_followups() with
queryset = FollowUp.objects.filter(survey__user=self.user).filter(survey__user__surveystatus_survey_status='active')
but I don't succeed to manage all the M2M relationships. I wrote the query above but issue also
Related Field got invalid lookup: surveystatus_survey_status
i read that a related_name can help to build reverse query but i don't understand why?
it's the first time i see return empty and what it needs to return above. Why this notation?
If you have clear explanations (more than the doc) I will very appreciate.
thanks
Quite a few things to answer here, I've put them into a list:
Your _get_all_active_surveys has the #property decorator but neither of the other two methods do? It isn't actually a property so I would remove it.
You are using a list comprehension to add your queryset objects to the m2m field, this is unnecessary as you don't actually want a list object and can be rewritten as e.g. self.surveys.add(*queryset)
You can comma-separate filter expressions as .filter(expression1, expression2) rather than .filter(expression1).filter(expression2).
You are missing an underscore in surveystatus_survey_status it should be surveystatus__survey_status.
Related name is just another way of reverse-accessing relationships, it doesn't actually change how the relationship exists - by default Django will do something like ModelA.modelb_set.all() - you can do reverse_name="my_model_bs" and then ModelA.my_model_bs.all()

How to define models in django that provides custom values based on pre-selection for a field?

Given the following model that stores the user's wish list for reading books:
class ReadingList(models.Model):
user_id = models.ForeignKey(UserInfo, on_delete=models.DO_NOTHING, null=False, blank=False, default=None, db_column='user_id')
book= models.CharField(max_length=255, blank=False)
creation_time = models.DateTimeField(blank=True)
class Meta:
unique_together = (('user_id', book),)
I want to create a model that helps in tracking the time spent in the reading the book on different days which looks something like this:
class ReadingTracker(models.Model):
user_id = models.ForeignKey(ReadingList, on_delete=models.DO_NOTHING, related_name='user', blank=False, db_column='user_id')
book= models.ForeignKey(ReadingList, on_delete=models.DO_NOTHING, related_name='book-to-read', blank=False, db_column='book')
time = models.DateTimeField(blank=True)
time_spent = models.floatfield()
On the client-side (corresponding to ReadingTracker) for both the fields user_id and book
I see that ReadingList object (1), ReadingList object (2), ... are listed. But, this is not working as expected.
What I want to achieve are the following:
For user_id field I want to see the something like dummy_uid1, dummy_uid2, ... to be listed.
Consider dummy_uid1 wants to read book1 and book2 whereas dummy_uid2 wants to read book1 and book3.
When dummy_uid1 is selected as user_id, I want only book1 and book2 to be listed for selection.
How do I define the model in django rest framework to achieve this?
Any suggestions related to the above would be much appreciated and thank you in advance.
There are two parts to this question:
If you want to see a different value than ReadingList object (1) then you need to define the __str__ value of your model, you can do this like so:
class ReadingList(models.Model):
...
def __str__(self):
return f'{self.user_id}' # return whatever string you want to display
If you want to just display the books for a particular user then you can use a filter() (see the Django documentation):
reading_list = ReadingList.objects.get(...)
ReadingTracker.objects.filter(user_id=reading_list)
However, I would add that you have a user_id on your ReadingList object which does seem to connect to a User model, but your user_id on ReadingTracker is a ForeignKey relation to ReadingList, which is confusing. I would suggest renaming the field or actually making it link to the User model (though this is unnecessary as you can still filter by User through the ReadingList model).

Using ForeignKey to sort with order_by and distinct not working

I'm trying to sort model Game by each title and most recent update(post) without returning duplicates.
views.py
'recent_games': Game.objects.all().order_by('title', '-update__date_published').distinct('title')[:5],
The distinct method on the query works perfectly however the update__date_published doesn't seem to be working.
models.py
Model - Game
class Game(models.Model):
title = models.CharField(max_length=100)
slug = models.SlugField(unique=True)
description = models.TextField()
date_published = models.DateTimeField(default=timezone.now)
cover = models.ImageField(upload_to='game_covers')
cover_display = models.ImageField(default='default.png', upload_to='game_displays')
developer = models.CharField(max_length=100)
twitter = models.CharField(max_length=50, default='')
reddit = models.CharField(max_length=50, default='')
platform = models.ManyToManyField(Platform)
def __str__(self):
return self.title
Model - Update
class Update(models.Model):
author = models.ForeignKey(User, models.SET_NULL, blank=True, null=True,) # If user is deleted keep all updates by said user
article_title = models.CharField(max_length=100, help_text="Use format: Release Notes for MM/DD/YYYY")
content = models.TextField(help_text="Try to stick with a central theme for your game. Bullet points is the preferred method of posting updates.")
date_published = models.DateTimeField(db_index=True, default=timezone.now, help_text="Use date of update not current time")
game = models.ForeignKey(Game, on_delete=models.CASCADE)
article_image = models.ImageField(default='/media/default.png', upload_to='article_pics', help_text="")
platform = ChainedManyToManyField(
Platform,
horizontal=True,
chained_field="game",
chained_model_field="game",
help_text="You must select a game first to autopopulate this field. You can select multiple platforms using Ctrl & Select (PC) or ⌘ & Select (Mac).")
See this for distinct reference Examples (those after the first will only work on PostgreSQL)
See this one for Reverse Query - See this one for - update__date_published
Example -
Entry.objects.order_by('blog__name', 'mod_date').distinct('blog__name', 'mod_date')
Your Query-
Game.objects.order_by('title', '-update__date_published').distinct('title')[:5]
You said:
The -update__date_published does not seem to be working as the Games are only returning in alphabetical order.
The reason is that the first order_by field is title; the secondary order field -update__date_published would only kick in if you had several identical titles, which you don't because of distinct().
If you want the Game objects to be ordered by latest update rather their title, omitting title from the ordering seems the obvious solution until you get a ProgrammingError that DISTINCT ON field requires field at the start of the ORDER BY clause.
The real solution to sorting games by latest update is:
games = (Game.objects
.annotate(max_date=Max('update__date_published'))
.order_by('-update__date_published'))[:5]
The most probable misunderstanding here is the join in your orm query. They ussually lazy-loading, so the date_published field is not yet available, yet you are trying to sort against it. You need the select_related method to load the fk relation as a join.
'recent_games': Game.objects.select_related('update').all().order_by('title', '-update__date_published').distinct('title')[:5]

Relationships - Rating one per user per address

Trying to craft a relationship like so:
The combination of user/address can only have one rating, where an address is part of a building which is also a foreign key on the rating.
At the moment I have this:
class Rating(models.Model):
buildingaddress = select2.fields.ForeignKey(BuildingAddress, overlay='Select the Building Address')
building = select2.fields.ForeignKey(Building, db_column='bin', null=True, overlay='Select your Building')
author = select2.fields.ForeignKey(User, overlay='Select the Author')
suggestion = models.TextField()
rating = models.IntegerField(null=True, blank=True)
This doesn't work correctly at the moment, because it allows multiple ratings per user per address.
you can add a unique_together constraint to make it so every address and user combination has to be unique
https://docs.djangoproject.com/en/dev/ref/models/options/#unique-together
unique_together = ('buildingaddress', 'author')

Sort by related field in Django ORM

I have a Notes and a NoteRefs fields where the NoteRefs has a foreign key to the Notes. I need to query the Notes but order by the related field (ie. the NoteRefs' start_ref field).
How might I do that through the django ORM? Here's kinda what works in SQL
SELECT
note.user_id,
note.content,
note.created,
note.modified
FROM noteref
INNER JOIN note
ON note.id = noteref.note_id
ORDER BY noteref.start_ref
I can't use Note.order_by('related_field'), because the related field isn't part of the Note Model. From what I can tell, that seems to be what the documentation says to do. How can I sort on the related field here?
EDIT: Model information
class Note(models.Model):
user = models.ForeignKey(User, db_index=True)
content = models.TextField()
created = models.DateTimeField(auto_now_add=True)
modified = models.DateTimeField(auto_now=True)
class NoteRef(models.Model):
note = models.ForeignKey(Note, db_index=True)
_order = models.IntegerField(default=0)
book = models.IntegerField(max_length=2)
start_ref = models.IntegerField(max_length=8, db_index=True)
end_ref = models.IntegerField(max_length=8, db_index=True)
ref_range = models.IntegerField()
passage = models.CharField(max_length=50)
You should try Note.objects.order_by("noterefs__start_ref")
The documentation doesn't make this very clear, as it uses a ForeignKey to self, but it works.
Now, the docs also warn against the possibility of duplicate objects showing up if you have multiple NoteRefs for a single Note, so you should double-check this.