Django how to deal with foreign keys in child tables - django

I am new in DJango. I have a database with three tables:
> -CRP
> - SubComponents of a CRP
> - Objectives of a SubComponent of a CRP.
I have the following models:
class period(models.Model):
period_code = models.IntegerField(primary_key=True,verbose_name='Period code')
period_desc = models.CharField(max_length=45,verbose_name='Period description')
period_current = models.IntegerField(verbose_name='Current period')
def __unicode__(self):
return self.period_desc
class Meta:
db_table = 'period'
class crp(models.Model):
crp_code = models.CharField(max_length=3,primary_key=True,verbose_name='CRP code')
crp_desc = models.CharField(max_length=45,verbose_name='CRP description')
def __unicode__(self):
return self.crp_desc
class Meta:
db_table = 'crp'
class subcomponent(models.Model):
crp_code = models.ForeignKey(crp,db_column='crp_code',related_name='subcomponent_crp_code',to_field='crp_code',primary_key=True,verbose_name='CRP code')
subc_code = models.CharField(max_length=3,primary_key=True,verbose_name='SubComponent Code')
subc_desc = models.CharField(max_length=45,verbose_name='SubComponent Description')
def __unicode__(self):
return self.subc_desc
class Meta:
db_table = 'subcomponent'
class objective(models.Model):
crp_code = models.ForeignKey(subcomponent,db_column='crp_code',related_name='objective_crp_code',to_field='crp_code',primary_key=True,verbose_name='CRP code')
subc_code = models.ForeignKey(subcomponent,db_column='subc_code',related_name='objective_subc_code',to_field='subc_code',primary_key=True,verbose_name='SubComponent Code')
obj_year = models.ForeignKey(period,db_column='obj_year',related_name='objective_obj_year',to_field='period_code',primary_key=True,verbose_name='Objective year')
obj_code = models.CharField(max_length=7,primary_key=True,verbose_name='Objective code')
obj_desc = models.CharField(max_length=45,verbose_name='Objective description')
def __unicode__(self):
return self.obj_desc
class Meta:
db_table = 'objective'
All works fine for the "subcomponent" model (reference to CRP); in the administration of "subcomponent" the user can pull down a CRP and add a subcomponent to it.
However, the model "objective" reference the model "subcomponent". In the administration of "objective" I would like the user to pull down and select a CRP, then filter the subcomponents of that CRP also in a pull down list. How can I do this?
Many thanks,
Carlos.

Take a look on django-smart-selects

Related

select related in Django

I am trying to accomplish something very similar to:
How to join 3 tables in query with Django
Essentially, I have 3 tables. In the Django REST we are showing table 3. As you see below (models.py), table 3 has company_name which is a foreign key of table 2 and table 2 is a foreign key of table 1. Both table 2 and 3 are linked by the table 1 ID. Table 1 contains the actual text, which we want to display in the API output, not the ID number.
Table 1: Manufacturer of Car -- Table 2: What the Car is -- Table 3: list of all cars
Models.py
Table 1:
class ManufacturerName(models.Model):
name_id = models.AutoField(primary_key=True)
company_name = models.CharField(unique=True, max_length=50)
class Meta:
managed = False
db_table = 'manufacturer_name'
Table 2:
class CarBuild(models.Model):
car_id = models.AutoField(primary_key=True)
car_icon = models.CharField(max_length=150, blank=True, null=True)
company_name = models.ForeignKey('ManufacturerName', models.DO_NOTHING, db_column='ManufacturerName')
class Meta:
managed = False
db_table = 'car_build'
Table 3:
class CarList(models.Model):
list_id = models.AutoField(primary_key=True)
company_name = models.ForeignKey('CarBuild', models.DO_NOTHING, db_column='CarBuild')
title = models.CharField(unique=True, max_length=100)
description = models.TextField()
class Meta:
managed = False
db_table = 'cars'
Within my views:
This is what I am trying, based on the foreign key relationships:
queryset = CarList.objects.all().select_related('company_name__company_name')
I get no errors when I save and run this, however, the ID is still being returned, and not the text associated with the foreign key relationships:
[
{
"list_id": 1,
"company_name": "http://127.0.0.1:8000/api/1/",
"title": "Really fast car you're driving, and this is dummy text",
Again, I would like to achieve getting the text associated with the company_name foreign key relationships from table 1 to show in the JSON.
serializer and viewset
class manufacturer_name(serializers.HyperlinkedModelSerializer):
class Meta:
model = manufacturer_name
fields = ('name_id', 'company_name')
class manufacturer_name(viewsets.ModelViewSet):
queryset = manufacturer_namee.objects.all()
serializer_class = manufacturer_name
class CarBuildViewSet(viewsets.ModelViewSet):
queryset = CarBuild.objects.all()
serializer_class = CarBuildSerialiser
class CarBuildSerialiser(serializers.HyperlinkedModelSerializer):
class Meta:
model = CarBuild
fields = ('car_id', 'car_icon', 'company_name')
class CarListSerialiser(serializers.HyperlinkedModelSerializer):
class Meta:
model = News
fields = ('list_id', 'company_name', 'title')
class CarListViewSet(viewsets.ModelViewSet):
serializer_class = CarList
def get_queryset(self):
queryset = News.objects.all().select_related('company_name__company_name')
return queryset
Based on detailed conversation to clear few details. Here is the answer.
You need to make small changes to your models as it was quite confusing to understand what you want to achieve.
Models:
class ManufacturerName(models.Model):
name_id = models.AutoField(primary_key=True)
company_name = models.CharField(unique=True, max_length=50)
class Meta:
managed = False
db_table = 'manufacturer_name'
class CarBuild(models.Model):
car_id = models.AutoField(primary_key=True)
car_icon = models.CharField(max_length=150, blank=True, null=True)
manufacturer = models.ForeignKey(ManufacturerName,on_delete=models.SET_NULL)
class Meta:
managed = False
db_table = 'car_build'
class CarList(models.Model):
list_id = models.AutoField(primary_key=True)
car = models.ForeignKey(CarBuild, on_delete=models.DO_NOTHING)
title = models.CharField(unique=True, max_length=100)
description = models.TextField()
class Meta:
managed = False
db_table = 'cars'
And then You need to adjust your serializers.
class CarListSerialiser(serializers.HyperlinkedModelSerializer):
company_name= serializers.SerializerMethodField(read_only=True)
class Meta:
model = CarList
fields = ('list_id', 'company_name', 'title')
def get_company_name(self, obj):
return obj.car.manufacturer.company_name
And you use it in your view:
class CarListViewSet(viewsets.ModelViewSet):
queryset = CarList.object.all()
serializer_class = CarListSerialiser

Django REST framework, dealing with related fields on creating records

Preliminary note: this is a rather newbie question, though I have not found a sufficient answer on StackOverflow; many similar questions, but not this one. So I am asking a new question.
The problem: I'm having difficulty creating records where one field is a foreign key to an existing record, and I do not know what I'm doing wrong in my code.
In my app there are two models in question, a one-to-many relationship between Company and BalanceSheet:
models:
class Company(models.Model):
cik = models.IntegerField(default=0, unique=True)
symbol = models.CharField(max_length=4, unique=True)
name = models.CharField(max_length=255, unique=True)
def __str__(self):
return self.symbol
class BalanceSheet(models.Model):
company = models.ForeignKey(Company,
null=True,
on_delete=models.CASCADE,
related_name='balance_sheets',)
date = models.DateField()
profit = models.BigIntegerField()
loss = models.BigIntegerField()
class Meta:
unique_together = (('company', 'date'),)
def __str__(self):
return '%s - %s' % (self.company, self.date)
serializers:
class BalanceSheetSerializer(serializers.ModelSerializer):
company = serializers.StringRelatedField()
class Meta:
model = BalanceSheet
fields = ('company','date','profit','loss')
class CompanySerializer(serializers.ModelSerializer):
class Meta:
model = Company
fields = ('cik', 'symbol', 'name')
Views:
class BalanceSheetCreate(generics.CreateAPIView):
model = BalanceSheet
queryset = BalanceSheet.objects.all()
serializer_class = BalanceSheetSerializer
urls include:
url(r'^(?P<symbol>[A-Z]{1,4})/create-balance-sheet/$', views.BalanceSheetCreate.as_view(),
name='create_balance_sheet'),
To this point, I have zero problem reading data. However, when trying to create records, I get errors I don't understand:
curl http://localhost:8000/financials/AAPL/create-balance-sheet/ -X POST -d "company=AAPL&date=1968-04-17&profit=1&loss=1"
IntegrityError at /financials/AAPL/create-balance-sheet/
null value in column "company_id" violates not-null constraint
Dropping the company data from that curl command results in the same error.
How do I get around this error? I thought I was telling the api what company I'm interested in, both explicitly in the url and in the post data.
Using python3.6, django 1.11, and djangorestframework 3.7.7
You get the IntegrityError because your code will try to create a new BalanceSheet without a company. That's because StringRelatedField is read-only (see docs) and therefore it's not parsed when BalanceSheetSerializer is used in write mode.
SlugRelatedField is what you need here:
class BalanceSheetSerializer(serializers.ModelSerializer):
company = serializers.SlugRelatedField(slug_field='symbol')
class Meta:
model = BalanceSheet
fields = ('company','date','profit','loss')
To answer my own question, here's what I wound up with. Thanks again go to dukebody.
models:
class Company(models.Model):
cik = models.IntegerField(default=0)
symbol = models.CharField(max_length=4)
name = models.CharField(max_length=255)
def __str__(self):
return self.symbol
class BalanceSheet(models.Model):
company = models.ForeignKey(Company,
null=True,
on_delete=models.CASCADE,
related_name='balance_sheets',)
date = models.DateField()
profit = models.BigIntegerField()
loss = models.BigIntegerField()
class Meta:
unique_together = (('company', 'date'),)
def __str__(self):
return '%s - %s' % (self.company, self.date)
serializers:
class CompanySerializer(serializers.ModelSerializer):
class Meta:
model = Company
fields = ('cik', 'symbol', 'name')
class BalanceSheetSerializer(serializers.ModelSerializer):
company = CompanySerializer(many=False)
class Meta:
model = BalanceSheet
fields = ('company', 'date', 'profit', 'loss')
def create(self, validated_data):
company_data = validated_data['company']
company, created = Company.objects.get_or_create(**company_data)
validated_data['company'] = company
sheet = BalanceSheet.objects.create(**validated_data)
return sheet
I also had to include the full company data within my curl statement as a nested dict.

Nested serializer with Django-Rest-Framework

I've been trying to use a nested serializer with DRF but it won't display the related item in the output.
Here's my model.py :
class Categorie(models.Model):
nom = models.CharField(max_length=100)
def __unicode__(self):
return unicode(self.nom)
class Item(models.Model):
nom = models.CharField(max_length=100)
disponible_a_la_vente = models.BooleanField(default = True)
nombre = models.IntegerField()
prix = models.DecimalField(max_digits=5, decimal_places=2)
history = HistoricalRecords()
categorie = models.ForeignKey(Categorie, models.CASCADE)
class Meta:
verbose_name = "item"
verbose_name_plural = u"inventaire"
ordering = ['categorie', 'nom']
def __unicode__(self):
return u'{nom} - {nombre}'.format(nom = self.nom, nombre = self.nombre)
and my serializers.py
class ItemSerializer(serializers.ModelSerializer):
class Meta:
model = Item
fields = ('nom',)
class CategorieSerializer(serializers.ModelSerializer):
items = ItemSerializer(many=True)
class Meta:
model = Categorie
fields = ('nom', 'id', 'items')
The view i'm currently testing is very basic :
class InventaireDetail(generics.RetrieveAPIView):
queryset = Categorie.objects.all()
serializer_class = CategorieSerializer
but it gives the error:
AttributeError: Got AttributeError when attempting to get a value for
field items on serializer CategorieSerializer. The serializer
field might be named incorrectly and not match any attribute or key on
the Categorie instance. Original exception text was: 'Categorie'
object has no attribute 'items'.
I've been looking for a while now but i can't get it working even with the help of the doc.
Categorie.items does not exist. By default the reverse relation would get the name Categorie.item_set. You can fix that in two ways.
EITHER: add related_name to your foreign key.
class Item(models.Model):
categorie = models.ForeignKey(Categorie, models.CASCADE, related_name='items')
OR: another solution is to change the CategorieSerializer
class CategorieSerializer(serializers.ModelSerializer):
items = ItemSerializer(many = True, read_only=True, source='item_set')

How do I order a ManyToManyField in Django and get them out in order?

I've got playlists with many songs in them, and I'd like those songs to stay in order when I get them from a queryset. I've tried the obvious thing - a 'through' model with order_by='song_order' - but that won't work, and trying to make it work with a proxy model on Song (see the code below) ends up throwing this error:
AttributeError: 'ManyToManyField' object has no attribute '_m2m_reverse_name_cache'
Currently, this is what it looks like:
class Song(models.Model):
sid = models.AutoField(primary_key=True)
id = models.CharField(max_length=60)
title = models.CharField(max_length=300)
artist_name = models.CharField(max_length=250)
artist_id = models.CharField(max_length=50)
mood = models.ForeignKey('Mood', related_name='songs_with_mood', blank=True, null=True)
def __unicode__ (self):
return "%s - %s" % (self.title, self.id)
class PlaylistSong(Song):
class Meta:
proxy = True
ordering = ['playlist_song_ordering__song_order']
# ^ this is the line responsible for the Attribute Error
class PlaylistSongs(models.Model):
song = models.ForeignKey("PlaylistSong", related_name='playlist_song_ordering')
playlist = models.ForeignKey("Playlist")
song_order = models.IntegerField()
class Meta:
db_table = "api_playlist_songs"
ordering = ['song_order']
class Playlist(models.Model):
owner = models.ForeignKey('UserProfile', related_name='owned_playlists')
title = models.CharField(max_length=70)
songs = models.ManyToManyField('Song', related_name='in_playlists', through='PlaylistSongs')
created = models.DateTimeField(auto_now_add=True)
class Meta:
ordering = ('-created',)
unique_together = ("owner", "title", "created",)

Creating an inline admin which displays the final model instead of the GenericForeignKey link table

The models below show a simple GenericForeignKey relationship. It has been set up in this way to allow an Image to be reused by any other model.
class Image(models.Model):
name = models.CharField(max_length=150)
desc = models.TextField(max_length=400)
resource = models.ImageField(upload_to='imgs/generic/%Y/%m/%d')
def __unicode__(self):
return self.name
class ImageItem(models.Model):
image = models.ForeignKey(Image, related_name='items', db_index=True)
content_type = models.ForeignKey(ContentType)
object_id = models.PositiveIntegerField(db_index=True)
object = generic.GenericForeignKey('content_type','object_id')
class Meta:
unique_together = (('image', 'content_type', 'object_id'),)
def __unicode__(self):
return u'%s [%s]' % (self.object, self.image)
class ImageInline(generic.GenericTabularInline):
model = ImageItem
At present using ImageInline within another model's admin will show a list box with all the images within the system.
Is it possible to get the inline admin to show the actual Image model instead, showing only the images assigned to the model being edited? Thus allowing the user to see immediately all the related info on the images attached to the model being added/edited. As well as being able to add/remove related images.
Thanks in advance for your help on this one.
You would have to change your code to:
class Image(models.Model):
name = models.CharField(max_length=150)
desc = models.TextField(max_length=400)
resource = models.ImageField(upload_to='imgs/generic/%Y/%m/%d')
content_type = models.ForeignKey(ContentType)
object_id = models.PositiveIntegerField(db_index=True)
object = generic.GenericForeignKey('content_type','object_id')
class Meta:
unique_together = (('resource', 'content_type', 'object_id'),)
def __unicode__(self):
return u'%s [%s]' % (self.object, self.resource)
class ImageInline(generic.GenericTabularInline):
model = Image
class MyModelAdmin(admin.ModelAdmin):
inlines = [
ImageInline,
]
This way when you go to add/change MyModel in admin you can add images as inlines.
UPDATE:
Solution with file browser field:
class Image(models.Model):
name = models.CharField(max_length=150)
desc = models.TextField(max_length=400)
resource = FileBrowseField("Image", max_length=200, directory="imgs/generic/%Y/%m/%d", extensions=['.jpg', '.gif', '.png'], format='Image')
content_type = models.ForeignKey(ContentType)
object_id = models.PositiveIntegerField(db_index=True)
object = generic.GenericForeignKey('content_type','object_id')
class Meta:
unique_together = (('resource', 'content_type', 'object_id'),)
def __unicode__(self):
return u'%s [%s]' % (self.object, self.resource)
class ImageInline(generic.GenericTabularInline):
model = Image
class MyModelAdmin(admin.ModelAdmin):
inlines = [
ImageInline,
]
More info about filebrowser field can be found here: http://code.google.com/p/django-filebrowser/wiki/installationfilebrowsefield