DRF polymorphic many-to-many with extra fields - python

I'm tinkering around with Django Rest Framework to build an api that can handle Bills of Materials (BoM) of electronic components.
I'm using django-polymorphic and django-rest-polymorphic so I can use polymorphic models for the components (they have shared attributes, but I still want to handle them in much the same way. The polymorphic models are great for this purpose).
All is well and good until I introduce many-to-many relations with a 'through' attribute. What I would like is a BoM that has several different components, each with a quantity, e.g. BoM1 has 2x470k resistor & 3x 100uF capacitor.
models.py: (pruned a bit to keep this post from being an epic novel)
class BoM(models.Model):
"""Bill of Materials: a list of all parts and their quantities for a given pedal"""
pedal = models.ForeignKey(Pedal, on_delete=models.CASCADE)
variant = models.CharField(max_length=100, blank=True)
electronic_components = models.ManyToManyField(
'ElectronicComponent', through='ElectronicComponentQty', blank=True)
class Meta:
verbose_name = 'Bill of materials'
verbose_name_plural = 'Bills of materials'
def __str__(self):
return str(self.pedal)
class ElectronicComponent(PolymorphicModel):
"""Shared data model for electronic components"""
value = models.CharField(max_length=20)
datasheet = models.FileField(upload_to='uploads/components', blank=True)
def __str__(self):
return self.value
class ElectronicComponentQty(models.Model):
"""Combination of resistor and quantity"""
bom = models.ForeignKey(BoM, on_delete=models.CASCADE)
component = models.ForeignKey(
ElectronicComponent, on_delete=models.CASCADE)
quantity = models.PositiveIntegerField(default=1)
class Meta:
verbose_name = 'Elecronic component quantity'
verbose_name_plural = 'Electronic component quantities'
class Resistor(ElectronicComponent):
"""Resistor data model"""
WATTAGES = [('1/4w', '1/4w'), ('1/8w', '1/8w')]
wattage = models.CharField(max_length=4, choices=WATTAGES, default='1/4w')
class Capacitor(ElectronicComponent):
"""Capacitors (all kinds)"""
VOLTAGE_RATINGS = [
('16V', '16V'),
('35V/50V', '35V/50V'),
]
CAP_TYPES = [
('ceramic disk', 'ceramic disk'),
('film', 'film'),
('electrolytic', 'electrolytic'),
('tantalum', 'tantalum'),
('other', 'other'),
]
capacitor_type = models.CharField(
max_length=20, choices=CAP_TYPES, default='film')
voltage_rating = models.CharField(
max_length=10, choices=VOLTAGE_RATINGS, blank=True)
serializers.py:
class ElectronicComponentSerializer(serializers.ModelSerializer):
class Meta:
model = ElectronicComponent
fields = '__all__'
class ElectronicComponentQtySerializer(serializers.ModelSerializer):
class Meta:
model = ElectronicComponentQty
fields = '__all__'
class BoMSerializer(serializers.ModelSerializer):
electronic_components = ElectronicComponentQtySerializer(
many=True, read_only=True)
class Meta:
model = BoM
fields = '__all__'
class ResistorSerializer(serializers.ModelSerializer):
class Meta:
model = Resistor
fields = '__all__'
class CapacitorSerializer(serializers.ModelSerializer):
class Meta:
model = Capacitor
fields = '__all__'
class ElectronicComponentPolySerializer(PolymorphicSerializer):
model_serializer_mapping = {
Resistor: ResistorSerializer,
Capacitor: CapacitorSerializer,
}
With this code I can create EletronicComponentQty objects no problem. However, when I try to list the BoM (through the serializer), I get:
AttributeError at /pedalparts/boms/
Got AttributeError when attempting to get a value for field bom on
serializer ElectronicComponentQtySerializer.
The serializer field might be named incorrectly and not match any
attribute or key on the Capacitor instance.
Original exception text was: 'Capacitor' object has no attribute
'bom'.
Anyone know how I can solve this? I'm open to any changes that make this work.

As electronic_components on the BoM model refers to the ElectronicComponent model it should not use the ElectronicComponentQtySerializer but one that can serialize the right instances, most likely the ElectronicComponentSerializer or the ElectronicComponentPolySerializer.

Related

Django REST Framework, Serializers: Additional data?

Good day,
I would like to ask, if there's a possibility to gain additional data inside my serializers?
These are my models...
models.py
class Chair(models.Model):
name = models.CharField(max_length=100, null=False, blank=False, unique=True)
bookable = models.BooleanField(default=False)
user_created = models.CharField(max_length=100)
date_created = models.DateField(auto_now_add=True)
class Booking(models.Model):
chair = models.ForeignKey(Chair, on_delete=models.CASCADE)
day = models.DateField()
user_name = models.CharField(max_length=100)
user_created = models.CharField(max_length=100)
date_created = models.DateField(auto_now_add=True)
and these my serializers...
serializers.py
class BookingSerializer(serializers.ModelSerializer):
class Meta:
model = Booking
fields = '__all__'
class ChairSerializer(serializers.ModelSerializer):
class Meta:
model = Chair
fields = '__all__'
When making a request inside js like this...
views.py
#api_view(['GET'])
def bookings_by_date(request, pk):
bookings = Booking.objects.filter(day=pk)
serializer = BookingSerializer(bookings, many=True)
return Response(serializer.data)
script.js
let url = '...here's my url for Booking...';
fetch(url)
.then((resp) => resp.json())
.then(function(data) {
// do something here
});
...I would like to get not only the id of the Chair (models.Foreignkey), but also it's name. My first thought was doing something like this...
class ChairSerializer(serializers.ModelSerializer):
class Meta:
model = Chair
fields = [
...
'chair',
'chair__name',
...
]
...but this doesn't seem to work! Does anyone know a solution for my problem? Thanks for all your help and have a great weekend!
You can use one of this two ways:
1-) Using SerializerMethodField. You can add readonly fields with this way. You should add get_<field_name> method or give a method name that you want to run for this field with name keyword. You can look the document for more details.
class BookingSerializer(serializers.ModelSerializer):
chair__name = serializers.SerializerMethodField()
class Meta:
model = Booking
fields = '__all__'
def get_chair_name(self, obj):
return obj.chair.name
2-) Using CharField with source attribute:
You can define basically this field fill from where.
class BookingSerializer(serializers.ModelSerializer):
chair__name = serializers.CharField(source='chair__name')
class Meta:
model = Booking
fields = '__all__'

Showing extra field on ManyToMany relationship in Django serializer

I have a ManyToMany field in Django, like this:
class Dictionary(models.Model):
traditional = models.CharField(max_length=50)
simplified = models.CharField(max_length=50)
pinyin_numbers = models.CharField(max_length=50)
pinyin_marks = models.CharField(max_length=50)
translation = models.TextField()
level = models.IntegerField()
frequency = models.IntegerField()
idiom = models.BooleanField()
child_char = models.ManyToManyField('Dictionary', through='DictionaryChildChar', null=True)
class Meta:
db_table = 'dictionary'
indexes = [
models.Index(fields=['simplified', ]),
models.Index(fields=['traditional', ]),
]
class DictionaryChildChar(models.Model):
class Meta:
db_table = 'dictionary_child_char'
from_dictionary = models.ForeignKey(Dictionary, on_delete=models.CASCADE, related_name="from_dictionary")
to_dictionary = models.ForeignKey(Dictionary, on_delete=models.CASCADE, related_name="to_dictionary")
word_order = models.IntegerField()
Currently, I have a serializer like this:
class FuzzySerializer(serializers.ModelSerializer):
pinyin = serializers.CharField(
required=False, source="pinyin_marks")
definition = serializers.CharField(
required=False, source="translation")
hsk = serializers.CharField(required=False, source="level")
class Meta:
model = Dictionary
fields = ["id", "simplified", "pinyin", "pinyin_numbers","definition", "hsk", "traditional", "child_char"]
depth = 1
This gives me a dictionary entry, as well as the child dictionary entries associated with it (as a Chinese word is made up of several Chinese characters)
However, I need to know what order these child characters are in, and hence why I have word_order.
I would like this word_order field to appear on the individual child_char - how do I write my serializer in such a way that this additional field is present? Would I need to make a separate serializer for child_char?
EDIT: I have tried this serializer, it doesn't work:
class FuzzyChildCharSerializer(serializers.ModelSerializer):
class Meta:
model = DictionaryChildChar
fields = ["word_order"]
Easiest way is to create a dedicated FuzzyChildCharSerializer and then connect it to your original serializer as a nested relationship:
class FuzzyChildCharSerializer():
class Meta:
model = DictionaryChildChar
fields = ["word_order"] # And whatever other fields you want
class FuzzySerializer():
child_char = FuzzyChildCharSerializer(many=True)
...
You could also write a SerializerMethodField.
It appears I had to bridge the connection via the glue table, which makes sense.
class FuzzyChildCharSerializer(serializers.ModelSerializer):
pinyin = serializers.CharField(
required=False, source="pinyin_marks")
definition = serializers.CharField(
required=False, source="translation")
hsk = serializers.CharField(required=False, source="level")
class Meta:
model = Dictionary
fields = ["id", "simplified", "pinyin", "pinyin_numbers","definition", "hsk", "traditional",]
class FuzzyChildCharSerializerGlue(serializers.ModelSerializer):
to_dictionary = FuzzyChildCharSerializer()
class Meta:
model = DictionaryChildChar
fields = '__all__'
class FuzzySerializer(serializers.ModelSerializer):
pinyin = serializers.CharField(
required=False, source="pinyin_marks")
definition = serializers.CharField(
required=False, source="translation")
hsk = serializers.CharField(required=False, source="level")
from_dictionary = FuzzyChildCharSerializerGlue(many=True)
class Meta:
model = Dictionary
fields = ["id", "simplified", "pinyin", "pinyin_numbers","definition", "hsk", "traditional", "from_dictionary"]
depth = 1
This provides each character with its given word order

AttributeError: 'AttributeValueAdmin' object has no attribute 'urls'

I have designed a table for attribute and product attributes. An attribute can have many values.
For example, an attribute called color can have values like Black, white, Grey, Maroon etc. For
this I designed a table such way
However when registering to the admin, I get AttributeError: 'AttributeValueAdmin' object has no attribute 'urls' error.
class Attribute(models.Model):
name = models.CharField(max_length=30, unique=True)
slug = models.SlugField(max_length=250, unique=True)
class Meta:
verbose_name = "Attribute"
verbose_name_plural = "Attributes"
def __str__(self):
return self.name
class ProductAttribute(SortableModel):
product = models.ForeignKey(Product,
related_name="productattribute",
null=True,
on_delete=models.CASCADE)
attribute = models.ManyToManyField(
Attribute,
through="AttributeValue"
)
class Meta:
ordering = ("sort_order",)
verbose_name = "Product Attribute"
verbose_name_plural = "Product Attributes"
class AttributeValue(SortableModel):
name = models.CharField(max_length=250)
value = models.CharField(max_length=100, blank=True, default="")
slug = models.SlugField(max_length=255)
productattribute = models.ForeignKey(ProductAttribute,
null=True,
related_name='productattribute',
on_delete=models.CASCADE)
attribute = models.ForeignKey(
Attribute, related_name="values", on_delete=models.CASCADE
)
class Meta:
ordering = ("sort_order", "id")
unique_together = ("slug", "attribute")
def __str__(self) -> str:
return self.name
admin.py
class ProductAdmin(admin.ModelAdmin):
model = models.Product
prepopulated_fields = {'slug': ('name',), }
class AttributeValueAdmin(admin.TabularInline):
model = models.AttributeValue
extra = 2
class AttributeAdmin(admin.ModelAdmin):
model = models.Attribute
prepopulated_fields = {'slug': ('name',), }
class ProductAttributeAdmin(admin.ModelAdmin):
# model = models.ProductAttribute
inlines = (AttributeValueAdmin, )
admin.site.register(models.Attribute, AttributeAdmin)
admin.site.register(models.AttributeValue, AttributeValueAdmin)
admin.site.register(models.ProductAttribute, ProductAttributeAdmin)
As per django docs on admin, the first step is to display the intermediate model by subclassing inline class for AttributeValue table like you have done
class AttributeValueInline(admin.TabularInline):
model = models.AttributeValue
extra = 2
Second step is to create admin views for both Attribute and ProductAttribute models.
class AttributeAdmin(admin.ModelAdmin):
inlines = (AttributeValueInline, )
prepopulated_fields = {'slug': ('name',), }
class ProductAttributeAdmin(admin.ModelAdmin):
inlines = (AttributeValueInline, )
Third step is to register your Attribute and ProductAttribute models
admin.site.register(models.Attribute, AttributeAdmin)
admin.site.register(models.ProductAttribute, ProductAttributeAdmin)
You don't need to register AttributeValue model as you can create/edit AttributeValue inline from either Attribute or ProductAttribute table.
For reference you can read the django docs
https://docs.djangoproject.com/en/3.0/ref/contrib/admin/#working-with-many-to-many-intermediary-models

How to make a ManyToMany serialization to be able to create a object in the POST request?

I'm trying to create a serialize to handle a ManyToMany relation, but it's not working. I have read the documentation and I probably doing something wrong. Also I have read the answers here.
Here are my models.
class Author(models.Model):
name = models.CharField(verbose_name="Name", max_length=255)
class Book(models.Model):
author = models.ForeignKey(
Author,
related_name="id_author",
blank=True,
null=True,
on_delete=models.PROTECT)
price = models.FloatField(verbose_name="Price")
class FiscalDocument(models.Model):
seller = models.ForeignKey(
User,
related_name="user_id",
on_delete=models.PROTECT)
book = models.ManyToManyField(Book)
My serializer:
class AuthorSerializer(serializers.ModelSerializer):
class Meta:
model = Author
fields = ('id', 'name')
class BookSerializer(serializers.ModelSerializer):
class Meta:
model = Book
fields = ('id', 'author', 'price')
def to_representation(self, instance):
response = super().to_representation(instance)
response['author'] = AuthorSerializer(instance.author).data
return response
class FiscalDocumentSerializer(serializers.ModelSerializer):
book = BookSerializer()
class Meta:
model = FiscalDocument
fields = ('id', 'seller', 'book')
def create(self, validated_data):
book_data = validated_data.pop('book')
fiscal_document = FiscalDocument.objects.create(**validated_data)
Book.objects.create(FiscalDocument=FiscalDocument,**medicine_data)
return fiscal_document
When I try to access the endpoint of the FiscalDocument, django-rest-framework is throwing an error:
Got AttributeError when attempting to get a value for field price on serializer BookSerializer. The serializer field might be named incorrectly and not match any attribute or key on the ManyRelatedManager instance. Original exception text was: ManyRelatedManager object has no attribute price.
If anyone can help XD.

Dynamic choices in Foreignkey Field in Django Rest Framework

I have just started learning Django Rest Framework and trying to make a simple API using Django rest Framework.
This is my models.py
from __future__ import unicode_literals
from django.db import models
class Student(models.Model):
created = models.DateTimeField(auto_now_add=True)
name = models.CharField(max_length=150, blank=False)
student_id = models.CharField(max_length=20, primary_key=True)
father_name = models.CharField(max_length=150)
mother_name = models.CharField(max_length=150)
class Meta:
ordering = ('student_id',)
class Subject(models.Model):
created = models.DateTimeField(auto_now_add=True)
subject_id = models.CharField(max_length=20, primary_key=True)
name = models.CharField(max_length=150)
class Meta:
ordering = ('subject_id',)
class Result(models.Model):
created = models.DateTimeField(auto_now_add=True)
grade = models.DecimalField(max_digits=5, decimal_places=3, blank=False)
student_id = models.ForeignKey(Student, on_delete=models.CASCADE)
subject_id = models.ForeignKey(Subject, on_delete=models.CASCADE)
class Meta:
ordering = ('created',)
And this is my serializers.py
from rest_framework import serializers
from models import *
class StudentSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = Student
fields = ('student_id', 'name', 'father_name', 'mother_name')
class SubjectSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = Subject
fields = ('subject_id', 'name')
class ResultSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = Result
fields = ('grade', 'student_id', 'subject_id')
In my "Result" model, I have two foreign keys; student_id and subject_id. This is how it looks like:
My questions is, how can I show the "name" field in the drop down menu in stead of showing "Student Object" and "Subject Object"?
I have tried with
STUDENT_CHOICES = [(each.student_id, each.name) for each in Student.objects.all()]
SUBJECT_CHOICES = [(each.subject_id, each.name) for each in Subject.objects.all()]
in the model's "choices=" field but it didn't work out.
Thanks in advance.
I think you're looking for this part of the DRF documentation.
Basically, your Django model's own representation is used. So for example, in your Student model you can add __str__ method:
# this is for Python 3, use __unicode__ on Python 2
def __str__(self):
return self.name
Meta options documentation for Django is here, look for model methods.

Categories