I'm doing an API from a existing database (which means it's not an option to change de DB schema) with Django and rest_framework. I have 2 tables, Foos and Bars.
foo_id
1
2
bar_id | foo_id (FK)
1 |1
2 |2
Bars model:
foo = models.ForeignKey('Foos', on_delete=models.CASCADE)
The Django Model changes de 'foo_id' FK into 'foo' only. There is a way to keep the FK with the '_id' suffix?
I would suggest specifying the field you want to use as a FK (foos_id in your case).
models.ForeignKey(
Foos,
on_delete=models.CASCADE,
to_field='id'
)
Fixed using:
foo_id = models.ForeignKey('Foos', on_delete=models.CASCADE, db_column='foo_id')
You can do this in the serializer with:
from rest_framework import serializers
class BarSerializer(serializers.ModelSerializer):
foo_id = serializers.PrimaryKeyRelatedField(source='foo')
class Meta:
model = Bar
fields = ['foo_id']
or even simply:
from rest_framework import serializers
class BarSerializer(serializers.ModelSerializer):
class Meta:
model = Bar
fields = ['foo_id']
Giving the model field the name foo_id however makes no sense: if you access bar_object.foo, you get a Foo object, not the id of a Foo object, so naming it foo_id is misleading.
Related
I would like to execute a single query in Django which retrieves related data, by foreign key, in multiple tables. At present I have to run a query on each table e.g. (House, Furniture, People) using the House number as a filter.
In SQL I can do this in one query like this:
SELECT house.number, house.number_of_rooms, furniture.type, people.name
FROM (house INNER JOIN furniture ON house.number = furniture.house_number)
INNER JOIN people ON house.number = people.house_number
WHERE (((house.number)="21"));
Can this be done in Django?
See example models below:
class House(models.Model):
number = models.CharField('House Number', max_length=10, blank=True, unique=True, primary_key=True)
number_of_rooms = models.IntegerField(default=1, null=True)
class Furniture(models.Model):
house_number = models.ForeignKey(House, on_delete=models.CASCADE, null=True)
type = models.CharField('Furniture Type', max_length=50)
class People(models.Model):
house_number = models.ForeignKey(House, on_delete=models.CASCADE, null=True)
first_name = models.CharField('First Name', max_length=50)
In your models add related_name arguments for foreign keys, so that you can retrieve the objects related to the House() instance.
class Furniture(models.Model):
house_number = models.ForeignKey(House, related_name='house_furniture', on_delete=models.CASCADE, null=True)
type = models.CharField('Furniture Type', max_length=50)
class People(models.Model):
house_number = models.ForeignKey(House, related_name='house_people', on_delete=models.CASCADE, null=True)
first_name = models.CharField('First Name', max_length=50)
Then run the migration using following commands.
python manage.py makemigrations
python manage.py migrate
Then create a new serializers.py module in the same app.
#import models Furniture, People, house
from rest_framework import serializers
class FurnitureSerializer(serializer.ModelSerializer):
class Meta:
model = Furniture
fields = ['type'] # if you want all the fields of model than user '__all__'.
class PeopleSerializer(serializer.ModelSerializer):
class Meta:
model = People
fields = ['first_name'] # if you want all the fields of model than user '__all__'.
class HouseSerializer(serializer.ModelSerializer):
house_furniture = FurnitureSerializer(many=True)
house_people = PeopleSerializer(many=True)
class Meta:
model = Furniture
fields = ['number', 'number_of_rooms', 'house_furniture', 'house_people']
Now, in your views.py you can simply query on model House and serializer the result with HouseSerializer().
#import models from models.py
#import serializer from serializers.py
from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework import status
from rest_framework.generics import ListAPIView
class ListHouseView(ListAPIView):
serializer_class = HouseSerializer
queryset = House.objects.filter() #here you can apply filters on the fields of house model and user using related_name you can filter on other related models as well.
Now, simply call ad this in your app's urls.py
url_pattern = [
path('list-house/', ListHouseView.as_view()),
]
Make sure that have a path in your project's urls.py to reach this app's urls.py.
The usual Django way of dealing with this is Queryset.prefetch_related() and iterating through Python (unless you're using Postgres, which has its own solution of ArrayAgg). Given your models, it'll cost three queries, but you won't have to deal with de-normalized row results.
h = House.objects.prefetch_related('furniture_set', 'people_set').get(number='21')
for furniture in house.furniture_set.all():
print(furniture)
for person in house.people_set.all():
print(people)
prefetch_related() caches the results and does the "joining" in Python once the queryset is evaluated, so iterating through the reverse relationships won't incur additional queries, and you're free to structure/serialize the data however you like. The raw SQL from this is something like:
SELECT house.number, house.number_of_rooms FROM house WHERE house.number = '1'
SELECT furniture.id, furniture.house_number_id, furniture.type FROM furniture WHERE furniture.house_number_id IN ('1')
SELECT people.id, people.house_number_id, people.first_name FROM people WHERE people.house_number_id IN ('1')
But Django does that behind-the-scenes so that you can just deal with a model instance in Python.
Is there a way of filling some particular fields in a model using a field value of another model object?
For example, I thought of the following scenario:
1 - there are some models
from django.db import models
class Supplier(models.Model):
supplier = models.Charfield(max_length=50, unique=True)
class Object(models.Model):
object = models.Charfield(max_length=50)
supplier = models.ForeignKey(Supplier, on_delete=models.CASCADE, to_field="supplier")
class Transaction(models.Model):
object_id = models.ForeignKey(Object, on_delete=models.CASCADE)
supplier = models.Charfield(max_length=50)
2 - Those models are serialized
from . import models
from rest_framework.serializers import ModelSerializer
class SupplierSerializer(ModelSerializer):
class Meta:
model = models.Supplier
fields = '__all__'
class ObjectSerializer(ModelSerializer):
class Meta:
model = models.Object
fields = '__all__'
class TransactionSerializer(ModelSerializer):
class Meta:
model = models.Transaction
exclude = ('supplier',)
3 - And there is a view
from . import models, serializers
from rest_framework.viewsets import ModelViewSet
class TransactionApiViewset(ModelViewSet):
queryset = models.Transaction.objects.all()
serializer_class = serializers.TransactionSerializer
When submiting a post with 'object_id' field to Transaction Api, I'd like that the 'supplier' field in Transaction Model autofills with the 'supplier' field value of Object object related to 'object_id'.
I'd appreciate any help.
I think you could use property(or cached_property) method for supplier instead of saving as a field:
class Transaction(models.Model):
object = models.ForeignKey(Object, on_delete=models.CASCADE)
#property
def supplier(self):
return self.object.supplier.supplier
If you need to keep supplier saved in Transaction table, then just keep the model definition as you posted, and add a validate method in serializer:
class TransactionSerializer(ModelSerializer):
def validate(self, data):
object = Object.objects.get(id=data["object_id"])
data["supplier"] = object.supplier.supplier
return data
That would automatically update Transaction with new object related supplier's supplier field.
I have two Django models that look like this:
class Team(models.Model):
name = models.CharField(max_length=30)
...
class Game(models.Model):
teamA = models.ForeignKey(Team, on_delete=CASCADE, related_name='teamA')
teamB = models.ForeignKey(Team, on_delete=CASCADE, related_name='teamB')
....
Game model has 2 foreign keys to Team model.
Of course, it is not possible for a team to play against itself, so I would like to prevent teamA and teamB from referring to the same record in DB.
So you can't create a game like this:
Game.objects.create(teamA=ChicagoBulls, teamB=ChicagoBulls)
What is the best way to do this?
You can implement a constraint in the .clean() method [Django-doc], and also at the database level with a CheckConstraint [Django-doc] (not all databases will however enforce this):
from django.core.exceptions import ValidationError
from django.db.models import F, Q
class Game(models.Model):
teamA = models.ForeignKey(Team, on_delete=CASCADE, related_name='teamA')
teamB = models.ForeignKey(Team, on_delete=CASCADE, related_name='teamB')
# …
def clean(self, *args, **kwargs):
if self.teamA_id == self.teamB_id:
raise ValidationError('The two teams should be different')
super().clean(*args, **kwargs)
class Meta:
constraints = [
models.CheckConstraint(
check=~Q(teamA=F('teamB')),
name='different_teams'
)
]
The .clean() method is not checked when you create an object with the model layer. It is however validated when you create a Game through a ModelForm [Django-doc].
You probably also might want to make the name of the Team unique, such that there can not be two teams with the same name:
class Team(models.Model):
name = models.CharField(max_length=30, unique=True)
# …
nested serializer showing null data
from rest_framework import serializers
from .models import PlayerTable, ChildTable
class ChildTableSerializer(serializers.ModelSerializer):
# x= ChildTable.objects.all().values
class Meta:
model = ChildTable
fields = ('season','goals','fk')
# fields =('fk',)
class PlayerTableSerializer(serializers.ModelSerializer):
player_details = ChildTableSerializer(many=True, read_only=True)
class Meta:
model = PlayerTable
fields = ('player_details',)
please help data getting by serializer is null
what is the field 'player-details'? It's not a field on your PlayerTable model. You need to use the name of the related field. In your case since you have a ForeignKey relationship ChildTable --> PlayerTable and you haven't specified the related_name, it's childtable_set. So if you do this it should work:
class PlayerTableSerializer(serializers.ModelSerializer):
childtable_set = ChildTableSerializer(many=True, read_only=True)
class Meta:
model = PlayerTable
fields = ('childtable_set',)
Alternatively, change your models naming to be more aligned with Django conventions:
class PlayerDetail(models.Model):
player = models.ForeignKey(Player, db_column="fk", related_name="player_details", null=True, blank=True, on_delete=models.CASCADE)
...
class Meta:
managed = False
db_table = "child_table"
class Player(models.Model):
name = models.CharField(db_column="player_name", ...)
class Meta:
db_table = "player_table"
then your serializer would work because the relation is player_details. This also has the advantage that when you do details.player you get the player object (now, you have to do details.fk but that actually doesn't return the foreign key value, it returns the Player object). Also your models have more pythonic names (Player not PlayerTable). Your code will be much more readable.
I have a table, that I made from 3 different models. And I want to filter them. But the problem is - all examples, that i've found - was about queryset and have only one model.
dict = [
{'name': Model1.objects.get(id=1), 'adress': Model2.objects.get(id=Model1.objects.get(id=1))},
{'name': Model1.objects.get(id=2),},
...
]
filter=FilterSet(queryset=???)
The point is - data from another models I got from Model1. But I can't build table on this model, because it don't have foreign keys to other 2 models.
Other words
class Model1(models.Model):
field1 = models.Charfield()
field2 = models.Charfield()
class Model2(models.Model):
field3 = models.Charfield()
field4 = models.ForeignKey(Model1)
...
Any suggestions? Because I don't think that making another model is a good solution.
You can use the ForeignKey to create a queryset that combines both models. Assume your models look like this (I have renamed your FK field for clarity):
class Model1(models.Model):
field1 = models.Charfield()
field2 = models.Charfield()
class Model2(models.Model):
field3 = models.Charfield()
model1 = models.ForeignKey(Model1, related_name='model2s')
Say you wanted to search through all Model2s that had a field3 of 'foo' and were connected to a Model1 with a field1 of bar. You would do this like so:
queryset = Model2.objects.filter(field3='foo', model1__field1='bar')
You could start from Model1 too, using the related_name field that is automagically created by Django. (There is a default way Django will name these if you don't include a name in the ForeignKey, but it's always best to be explicit.)
This will return any Model1 that has a field1 of 'bar' and is connected to any Model2 with a field3 of 'foo'.
queryset = Model1.objects.filter(field1='bar', model2s__field3='foo')