How to access data across M2M tables in Django? - python

What is the 'best practice' way of accessing data across a 1 (or more) many-to-many tables?
This is incredibly difficult for me as I am not sure what I shuld be googling/looking up.
I have attached a diagram of my data model. I am able to query data for 'C' related ot a user, by utilizing serializers.
there has to be a simpler way of doing this (I'm hoping).
Doing it with serializers seems incredibly limiting. I'd like to access a user's 'B' and 'C' and transform the object to only have a custom structure and possible unique values.
Any direction is much appreciated. Pretty new to Django, so I apologize for this newb type of question.

Here is an example of M2M relation using Django:
class User(models.Model):
name = models.CharField(...)
class Song(models.Model)
title = models.CharField(...)
users_that_like_me = models.ManyToManyField('User', ..., related_name='songs_that_i_like')
So a User can like many Songs and a Song can be liked by many Users.
To see all the songs a user liked, we can do:
user = User.objects.get(id='<the-user-id>')
liked_songs = user.songs_that_i_like.all()
And to see all the users who like a particular song we can similarly do:
song = Song.objects.get(id='<the-song-id>')
users_that_like_this_song = song.users_that_like_me.all()
Both liked_songs and users_that_like_this_song are actually querysets, meaning we can do some Django magic on them.
For example, to find all users named Jon that liked this song we can do:
users_that_like_this_song.filter(name='Jon')
We can also add some property shortcuts to our Models to help with some common tasks, for example:
class User(models.Model):
...
#property
def number_of_liked_songs(self):
return self.songs_that_i_like.count()
Then we can do:
user = User.objects.get(id='<the-user-id>')
number_of_songs_i_like = user.number_of_liked_songs
There's much more we can do with Django - if you're looking for something specific let us know.

Related

django query accessing foreign key data

Apology if this is a duplicate but I could not find any satisfying answer for this case. Maybe I am doing something fundamentally wrong, like need to define models in a different way.I am also very new to django.So suggestions will be really helpful.
Suppose I have my models like this:
from django.contrib.auth.models import Group
class Team(Group):
description = models.CharField(max_length=30)
class Players(models.Model):
name = models.CharField(max_length=60)
team = models.ForeignKey(Team, on_delete=models.CASCADE)
Now if I for example, want a queryset of all players by the Team model what can I do?
I have the foreign key in the Players model and not in Team model. So something like Team.objects.filter(logic) is needed to get all players. But exactly what logic will work here?
Any help will be much appreciated.
Django ORM also can filter by relation. these kind of filtering is valid for all kind of relations.
players = Players.objects.filter(team__id = 1)
in case you have the team object.
players = Players.objects.filter(team = team)
Django allows you get access to related objects in reverse way. It calling reverse relation. And related manager objects using for it. You can find more details here and here. In your specifc case you can get players using players_set team's attribute:
team = Team.objects.get(pk=1)
players = team.players_set.all()

How to assign Django object ownership without explicitly declaring an owner field on all models?

I'm currently trying to figure out per user object permissions for our Django website API.
I have several models with sensitive information, that I need to be able to filter on a user basis.
For a simplified example of one of the models:
Restaurant, main customer of the website.
User, each user gets assigned a restaurant when the user account is
created. As such, a restaurant can have many users and they all
should only be able to access that restaurant's information.
Oven, which belong to a specific restaurant. A restaurant can have
many ovens.
Recipe, which belong to an oven. An oven can have many different
recipes.
Recipe Results, which belong to a recipe. There can be many different
Recipe Results belonging to the same Recipe (different ingredients
tried, etc).
There are at least 12+ different models. All models from a particular restaurant have to be hidden from other restaurants, we don't want them to be able to look at other restaurant recipes after all!
Not all models have a user = models.ForeignKey(User)
Without having to go into each one of my models and declaring owner = models.ForeignKey(User), is there a way to filter them in my API List Views and Detail Views?
Currently my List API View looks like this (simplified example):
class RecipeResultsListAPIView(ListAPIView):
queryset = RecipeResults.objects.all()
queryset = queryset.prefetch_related('oven')
serializer_class = RecipeResultsListSerializer
filter_backends = (DjangoFilterBackend,)
filter_fields = ('id', 'time', 'oven', 'recipe_name', 'recipe_description')
pagination_class = ExpertPageNumberPagination
def list(self, request):
user = User.objects.get(username=request.user)
restaurant = Restaurant.objects.get(user=user)
ovens = Oven.objects.filter(restaurant=restaurant)
queryset = RecipeResults.objects.filter(oven__in=ovens)
serializer = RecipeResultsListSerializer(queryset, many=True, context={'request':request})
return Response(serializer.data)
And the model for that looks like this:
class RecipeResults(models.Model):
time = models.DateTimeField()
oven = models.ForeignKey(Oven, on_delete=models.CASCADE)
recipe_name = models.CharField(max_length=20)
recipe_description = models.CharField(max_length=50)
def __str__(self):
return str(self.time) + ': ' + self.recipe_name + ' = ' + self.recipe_description
def __key(self):
return self.oven, self.time, self.recipe_name
def __eq__(self, y):
return isinstance(y, self.__class__) and self.__key() == y.__key()
def __hash__(self):
return hash(self.__key())
class Meta:
unique_together=(('time','recipe_name', 'oven-'),)
Specifically looking at the modified list method, currently this works properly to filter API call results to display only those Recipe Results that belong to the user that is logged in.
What I'm trying to figure out is if there's an easier way to do this, as for each model I would have to trace back ownership to the specific restaurant which would get confusing fast as I have 12+ different models.
What I'm not sure is if declaring "owner = models.ForeignKey(User)" on each of those models is the way to go. It feels like it would create many extra steps when retrieving the data.
I have also tried
class IsOwnerOrAdmin(BasePermission):
"""
Custom permission to only allow owners of an object to see and edit it.
Admin users however have access to all.
"""
def has_object_permission(self, request, view, obj):
# Permissions are only allowed to the owner of the snippet
if request.user.is_staff:
return True
return obj.user == request.user
But this didn't seem to filter properly, and besides, not all of the models have a user field assigned to them.
Please keep in mind I'm a junior developer and I'm learning a lot as I go. I'm only working on the API side of the company. The website and schema is already a work in progress and other systems depend on it, and so I'm trying not to modify the schema or models too much (I would like to avoid this if possible, but will do it if it's the only way). I was also brought in just to work on the API at first. The company understands I'm a junior developer and I'm extremely grateful to have been given the opportunity to grow while learning this project, but this one issue seems to be giving me a lot more trouble than actually building the rest of the API for the website.
I would greatly appreciate any help I can get with this!
I think you might benefit from model inheritance in this case.
You can define a base model for your owner-affected objects.
An example can look like:
class OwnedModel(models.Model):
owner = models.ForeignKey(User)
class Meta:
abstract = True
Then you can simply add this as the base for your other models:
class SomeModel(OwnedModel):
"""
This class already has the owner field
"""
A big downside of this approach is that you will still need a migration that will alter every involved table.
If you aren't allowed to do that, you might be able to do it with a loose, non relational approach, for example with django's permission model. You can assign automatically generated permission strings, eg: myapp.mymodel.pkey:
A final alternative is this third party source app that handles things for you: django-guardian

Understanding normalization tables in Django's ORM

I'm trying to learn Django from a background of coding the database schema directly myself. I want to understand how I should be effectively using the database abstraction tools to normalize.
As a contrived example, let's say I have a conversation that can ask questions on 3 subjects, and each question is complicated enough to warrant its own Class.
Class Conversation(models.Model):
partner = models.CharField()
Class Weather_q(models.Model):
#stuff
Class Health_q(models.Model):
#stuff
Class Family_q(models.Model):
#stuff
So let's say I want to have 2 conversations:
Conversation 1 with Bob: ask two different weather questions and one question about his health
Conversation 2 with Alice: ask about the weather and her family
Usually, I would code myself a normalization table for this:
INSERT INTO Conversation (partner) values ("Bob", "Alice"); --primary keys = 1 and 2
INSERT INTO NormalizationTable (fk_Conversation, fk_Weather_q, fk_Health_q, fk_Family_q) VALUES
(1,1,0,0), -- Bob weather#1
(1,2,0,0), -- Bob weather#2
(1,0,1,0), -- Bob health#1
(2,1,0,0), -- Alice weather#1
(2,0,0,1); -- Alice family#1
Do I need to explicitly create this normalization table or is that discouraged?
Class NormalizationTable(models.Model):
fk_Conversation = models.ForeignKey(Conversation)
fk_Weather_q = models.ForeignKey(Weather)
fk_Health_q = models.ForeignKey(Health)
fk_Family_q = models.ForeignKey(Family)
Then I then wanted to execute the conversations. I wrote a view like this (skipping exception catching and logic to iterate through multiple questions per conversation):
from myapp.models import Conversation, Weather_q, Health_q, Family_q
def converse(request):
#get this conversation's pk
#assuming "mypartner" is provided by the URL dispatcher
conversation = Conversation.objects.filter(partner=mypartner)[0]
#get the relevant row of the NormalizationTable
questions = NormalizationTable.objects.filter(fk_Conversation=conversation)[0]
for question in questions:
if question.fk_Weather_q:
return render("weather.html", Weather_q.objects.filter(pk=fk_Weather_q)[0])
if question.fk_Health_q:
return render("health.html", Health_q.objects.filter(pk=fk_Health_q)[0])
if question.fk_Family_q:
return render("family.html", Family_q.objects.filter(pk=fk_Family_q)[0])
Considered holistically, is this the "Django" way to solve this kind of normalization problem (N objects associated with a container object)? Can I make better use of Django's inbuilt ORM or other tools?
Leaving aside "normalization tables" (the term is unfamiliar to me), this is what I think is a "djangish" way of solving your problem. Please note that I went with your statement "each question is complicated enough to warrant its own Class". For me this means that every type of question necessitate its own unique fields and methods. Otherwise I would create a single Question model connected to a Category model by a ForeignKey.
class Partner(models.Model):
name = models.CharField()
class Question(models.Model):
# Fields and methods common to all kinds of questions
partner = models.ForeignKey(Partner)
label = models.CharField() # example field
class WeatherQuestion(Question):
# Fields and methods for weather questions only
class HealthQuestion(Question):
# Fields and methods for health questions only
class FamilyQuestion(Question):
# Fields and methods for family questions only
This way you would have a base Question model for all the fields and methods common to all questions, and a bunch of child models for describing different kinds of questions. There is an implicit relation between base model and its child models, maintained by Django. This gives you an ability to create a single queryset with different questions, no matter their type. Items in this queryset are of Question type by default, but can be converted to a particular question type by accessing a special attribute (for example a healthquestion attribute for HealtQuestions). This is described in detail in the "Multi-table model inheritance" section of Django documentation.
Then in a view you can get a list of (different types of) questions and then detect their particular type:
from myapp.models import Question
def converse(request, partner_id):
question = Question.objects.filter(partner=partner_id).first()
# Detect question type
question_type = "other"
question_obj = question
# in real life the list of types below would probably live in the settings
for current_type in ['weather', 'health', 'family']:
if hasattr(question, current_type + 'question'):
question_type = current_type
question_obj = getattr(question, current_type + 'question')
break
return render(
"questions/{}.html".format(question_type),
{'question': question_obj}
)
The code for detecting question type is quite ugly and complicated. You could make it much simpler and more generic using the InheritanceManager from django-model-utils package. You would need to install the package and add the line to the Question model:
objects = InheritanceManager()
Then the view would then look something like this:
from myapp.models import Question
def converse(request, partner_id):
question = Question.objects.filter(partner=partner_id).select_subclasses().first()
question_type = question._meta.object_name.lower()
return render(
"questions/{}.html".format(question_type),
{'question': question}
)
Both views select only a single question - the first one. That's how the view in your example behaved, so I went with it. You could easily convert those examples to return a list of questions (of different types).
I'm not familiar with the term normalization table, but I see what you're trying to do.
What you've described is not, in my opinion, a very satisfactory way to model a database. The simplest approach would be to make all questions part of the same table, with a "type" field, and maybe some other optional fields that vary between the types. In that case, this becomes very simple in Django.
But, OK, you said "let's say... each question is complicated enough to warrant its own class." Django does have a solution for that, which is generic relations. It would look something like this:
class ConversationQuestion(models.Model):
conversation = models.ForeignKey(Conversation)
content_type = models.ForeignKey(ContentType)
question_id = models.PositiveIntegerField()
question = GenericForeignKey('content_type', 'question_id')
# you can use prefetch_related("question") for efficiency
cqs = ConversationQuestion.objects.filter(conversation=conversation)
for cq in cqs:
# do something with the question
# you can look at the content_type if, as above, you need to choose
# a separate template for each type.
print(cq.question)
Because it's part of Django, you get some (but not total) support in terms of the admin, forms, etc.
Or you could do what you've done above, but, as you noticed, it's ugly and doesn't seem to capture the advantages of working with an ORM.

Specify relationship verbs in Django at runtime?

How do you identify model relationship verbs in Django?
So, lets say I have a model Movie, and want fields for Director and Writer. I don't want to have two distinct tables for these roles, like this:
...because it violates database best practices (normal forms, etc.), and doesn't keep things DRY. Instead I want a single Person model, via a ManyToMany relationship with Movie, like this:
How do I allow these verbs, marked in the diamonds, to be specified at runtime?
For example, in the Django Admin, Movie should have a Person field, with some place to enter a Role, e.g. written by or directed by. I don't want to have fields in Movie for each possible role: I don't want a director or writer or animal_trainer field, for example. There might be hundreds... some of them duplicates.
In Rails, I'd just make a join table with an extra field Role, but I assume Django has a better way.
I'm sure this is documented somewhere in the Django docs, or maybe just in general SQL docs somewhere, but I guess I don't know the correct search terms to Google. A link to relevant docs would be sufficient, if they exist. Thanks!
I think your best option in (for this context) is simply having 2 models, Movie and person, and having 2 foreign keys in the Movie table, one for writer, one for director, since every movie will have both of those fields, e.g.
def Person(models.Model):
...
def Movie(models.Model):
writer = models.ForeignKey(Person, related_name='movies_written')
director = models.ForeignKey(Person, related_name='movies_directed')
...
EDIT
With your change in question, I believe the best answer to this is from https://docs.djangoproject.com/en/dev/topics/db/models/#extra-fields-on-many-to-many-relationships
def Person(models.Model):
...
def Movie(models.Model):
...
workers = models.ManyToManyField('Person', through='WorkedOn')
# model in case you want to specify anything specific for roles for every person
def Role(models.Model):
...
# can have extra fields, e.g. dates worked, pay, etc.
def WorkedOn(models.Model):
person = models.ForeignKey('Person')
movie = models.ForeignKey('Movie')
role = models.ForeignKey('Role')
...

Understanding Querysets, creating the perfect database relationship

I'm am currently trying to figure out the best way to structure my database schema based on a few models. I'll try and explain this the best I can so I can work out the best way to tackle the problem.
Firstly, I have 3 models that are "related"
User which is extended to contain the field api_key, Campaign and finally Beacon.
User's can have many Campaign's but a Campaign can only relate to one User my first choice here was to have Campaign have a foreign key to User, correct me if I'm wrong, but I feel that is the best choice there. Likewise, Campaign can have many Beacon's but a Beacon can only relate to one Campaign at a time. Again, I'm presuming that a foreign key here would work the best.
The issue arises when I try and query the Beacon's that relate to any given Campaign. I wish to return all Beacon's that relate to the User whilst also getting the data for Campaign.
I wish to return a JSON string like the following:
{
XXXX-YYYYY: {
message: "Hello World",
destination: "http://example.com"
}
XXXX-YYYYY: {
message: "Hello World",
destination: "http://example.com"
}
}
XXXX-YYYYY being the Beacon.factory_id and message/destination being Campaign.message and Campaign.destination
I'm thinking Queryset's here, but I've never worked with them before and it just confused me.
According to your question, you have something like this:
class User(models.Model):
pass
class Campaign(models.Model):
user = models.ForeignKey(User, verbose_name="Attached to")
message = models.CharField()
destination = models.CharField()
class Beacon(models.Model):
factory_id = models.CharField()
campaign = models.ForeignKey(Campaign, verbose_name="Campaign")
You can follow ForeignKey "backward", by using campaign_set generated attribute:
If a model has a ForeignKey, instances of the foreign-key model will have access to a Manager that returns all instances of the first model. By default, this Manager is named FOO_set, where FOO is the source model name, lowercased.
So you can query your Beacon model like this:
beacon = Beacon.objects.get(factory_id="XXXX-YYYYY")
# Get every campaigns related and only relevant fields (in a list of dict)
campaigns = beacon.campaign_set.all().values('message', 'destination')
for campaign in campaigns:
print(campaign['message'])
print(campaign['destination'])
For your dictionary, it is impossible to make it exactly like this. You can't have a duplicate key.
I wish to return all Beacons that relate to the User whilst also getting the data for Campaign
beacons = Beacon.objects.filter(campaign__user=user).select_related('campaign')
You can then easily process this into your desired data structure.
I'm thinking Querysets here, but I've never worked with them before and it just confused me
A QuerySet is simply how the Django ORM represents a query to your database that results in a set of items. So the above is a QuerySet, as is something as simple as User.objects.all(). You can read some introductory material about QuerySets in the documentation.

Categories

Resources