Django Model Serializer manyToMany relationship - django

I have problem with Django 4.1.2 (Python 3.8.9).
I have 2 entities: Idea and IdeaTheme, which have manyToMany relationship.
class Idea(models.Model):
short_name = models.CharField(max_length=70)
description = models.TextField(null=True,
blank=True,
default='')
class State(models.IntegerChoices):
CREATED = 0, _("Создано")
TEAM_SEARCHING = 1, _("Поиск команды")
IN_PROCESS = 2, _("Реализация")
READY = 3, _("Готово")
state = models.IntegerField(choices=State.choices,
default=State.CREATED)
wanted_skills = models.ManyToManyField(to=UserSkill)
themes = models.ManyToManyField(to=IdeaTheme,
null=True,
blank=True)
class IdeaTheme(models.Model):
name = models.CharField(max_length=70,
verbose_name="Название")
background_photo = models.ImageField(blank=True,
null=True,
verbose_name="Изображение для фронта")
Also I have ModelSerializer
class IdeaSerializer(serializers.ModelSerializer):
class Meta:
model = Idea
fields = '__all__'
depth = 1
extra_kwargs = {
'state': {
'read_only': True
},
'pk': {
'read_only': True
}
}
And when I using CreateAPIView in order to create Idea I face the problem, that I must provide IdeaTheme pks. But in the model there are Null and Blank option and I want to create idea without IdeaThemes.
I tried to specify IdeaTheme serializer inside IdeaSerializer like this, but Django still require not empty array of IdeaTheme.
themes = IdeaThemeSerializer(many=True, required=False)
Could someone help me, please, who already has experience using built-in serializers?

Related

Django nested serializer giving me None for all fields

So I'm working with Django and djangorestframework (versions 3.2.12 and 3.12.4, respectively) and I'm running into an issue with nested serializers.
I've got FantasyLeague and FantasyLeagueSettings models. FantasyLeagueSettings has a FK to FantasyLeague
models.py
class FantasyLeague(models.Model):
name = models.CharField(max_length=100)
managers = models.ManyToManyField(User)
yahoo_league_id = models.CharField(max_length=20, blank=True, null=True)
class FantasyLeagueSettings(models.Model):
league = models.ForeignKey(FantasyLeague, on_delete=models.CASCADE, related_name='settings')
max_teams = models.IntegerField(blank=True, null=True)
num_playoff_teams = models.IntegerField(blank=True, null=True)
playoff_start_week = models.IntegerField(blank=True, null=True)
I'm serializing them like so:
serializers.py
class FantasyLeagueSettingsSerializer(serializers.ModelSerializer):
class Meta:
model = FantasyLeagueSettings
fields = ['max_teams', 'num_playoff_teams', 'playoff_start_week']
class FantasyLeagueSerializer(serializers.ModelSerializer):
managers = UserSerializer(read_only=True, many=True)
settings = FantasyLeagueSettingsSerializer(many=False, read_only=True)
class Meta:
model = FantasyLeague
fields = ['id', 'name', 'managers', 'settings', 'yahoo_league_id' ]
But for some reason FantasyLeagueSerializer(league).data gives me a value of None for all of the settings fields (even though the settings data exists)
Testing code:
league = FantasyLeague.objects.get(pk=1)
print('🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥')
print(FantasyLeagueSerializer(league).data)
# FantasyLeagueSerializer(league).data: {'id': 1, 'name': 'PLAYING FOR KEEPS', 'managers': [OrderedDict([('id', 1), ('username', 'Smurflo'), ('settings', OrderedDict([('selected_theme', 'kanagawa')])), ('is_staff', True)])], 'settings': OrderedDict([('max_teams', None), ('num_playoff_teams', None), ('playoff_start_week', None)]), 'yahoo_league_id': '461051' }
# Note how all of the settings fields are None
# Even though league.settings clearly has data and FantasyLeagueSettingsSerializer works
print('🎆🎆🎆🎆🎆🎆🎆🎆🎆🎆🎆🎆🎆🎆🎆🎆🎆🎆🎆')
print(FantasyLeagueSettingsSerializer(league.settings.all()[0]).data)
# {'max_teams': 12, 'num_playoff_teams': 6, 'playoff_start_week': 15}
Anyone know what's going on here? What am I missing?
The reason for the problem is due to the uncoordinated relationship between FantasyLeague and FantasyLeagueSettings.
if instances of FantasyLeague can have more than one settings, then you should change many=False to many=True in settings field of FantasyLeagueSerializer serializer.
class FantasyLeagueSerializer(serializers.ModelSerializer):
...
settings = FantasyLeagueSettingsSerializer(many=True, read_only=True)
...
but if FantasyLeague's instances can have at most one settings, then you should change league's type to OneToOneField in FantasyLeagueSettings model.
class FantasyLeagueSettings(models.Model):
league = models.OneToOneField(FantasyLeague, on_delete=models.CASCADE, related_name='settings')
...

IntegrityError: null value in column "invoiceOwner_id" violates not-null constraint

In my django app, i'm having difficulties whenever i want to add a new object that uses the table paymentInvoice.
The error i'm getting from my api looks like this
IntegrityError at /api/clients/invoice/
null value in column "invoiceOwner_id" violates not-null constraint
DETAIL: Failing row contains (10, INV-0006, Lix, 2020-08-04, 1, Pending, 3000, null).
NB: I haven't created the field invoiceOwner_id, postgres automatically added it or rather is using it as a representation for my invoiceOwner field
class Purchaser(models.Model):
name = models.CharField(max_length=50)
phone = models.CharField(max_length=20, unique=True)
email = models.EmailField(max_length=255, unique=True, blank=True)
image = models.ImageField(default='default.png', upload_to='customer_photos/%Y/%m/%d/')
data_added = models.DateField(default=datetime.date.today)
def __str__(self):
return self.name
class paymentInvoice(models.Model):
invoiceNo = models.CharField(max_length=50, unique=True, default=increment_invoice_number)
invoiceOwner = models.ForeignKey(Purchaser, on_delete=models.CASCADE, related_name="invoice_detail")
product = models.CharField(max_length=50, blank=True)
date = models.DateField(default=datetime.date.today)
quantity = models.PositiveSmallIntegerField(blank=True, default=1)
payment_made = models.IntegerField(default=0)
def __str__(self):
return self.invoiceOwner.name
serilizers file
class paymentInvoiceSerializer(serializers.ModelSerializer):
invoiceOwner = serializers.SerializerMethodField()
class Meta:
model = paymentInvoice
fields = '__all__'
def get_invoiceOwner(self, instance):
return instance.invoiceOwner.name
views file
class paymentInvoiceListCreateView(ListCreateAPIView):
serializer_class = paymentInvoiceSerializer
queryset = paymentInvoice.objects.all().order_by('-date')
GET result from api call.
{
"id": 1,
"invoiceOwner": "Martin",
"invoiceNo": "INV-0001",
"product": "",
"date": "2020-08-04",
"quantity": 1,
"payment_made": 0
}
Tried passing below as POST but got the main error
{
"invoiceOwner": "Becky",
"product": "Lix",
"quantity": 1,
"payment_made": 3000
}
"invoiceOwner" in your serializers.py is a SerializerMethodField which is readonly
that's why you get an error, you have to define the create method yourself
As I said in comment: You need to explicitly override the create method in your serializer since your model has foreign key invoiceOwner, just to create that instance first as a Purchaser instance.
You can try the code below:
class paymentInvoiceSerializer(serializers.ModelSerializer):
invoiceOwner = serializers.SerializerMethodField()
class Meta:
model = paymentInvoice
fields = '__all__'
def get_invoiceOwner(self, instance):
return instance.invoiceOwner.name
def create(self, validated_data):
purchaser_name = validated_data.get("invoiceOwner")
purchaser = Purchaser(name=purchaser_name,
# you need to have phone, email, since these fields are unique,
# they can't remain null
)
purchaser.save()
return paymentInvoice.objects.create(invoiceOwner = purchaser, **validated_data)

How to create a custom list field when using Django Rest Framework

I have the following model and I would like to add a custom field called choices which will be a list of choices.
class HPIQuestionBank(models.Model):
label = models.CharField(
max_length=200,
db_index=True,
blank=True,
null=True)
template = models.ForeignKey(
HPIFilter, blank=True, null=True, on_delete=models.CASCADE, default='')
I have implemented the following in the serializers.
class CheckBoxesListField(serializers.ListField):
child = serializers.CharField(allow_null = True, allow_blank=True)
class TemplateQuestionBankSerializer(serializers.ModelSerializer):
answer_type = serializers.CharField(allow_null = True, allow_blank=True)
checkboxes = CheckBoxesListField()
hpianswers_set =TemplateAnswerSerializer(many=True)
class Meta:
model = HPIQuestionBank
fields = ['id','label','hpianswers_set','answer_type','checkboxes']
I'm using the serializer on my GET method. When I attempt to make a request I get the following error:
AttributeError at /api/clinic2/history/template/6/
Got AttributeError when attempting to get a value for field `checkboxes` on serializer `TemplateQuestionBankSerializer`.
The serializer field might be named incorrectly and not match any attribute or key on the `HPIQuestionBank` instance.
Original exception text was: 'HPIQuestionBank' object has no attribute 'checkboxes'.
If you need to read data only you can try:
class Meta:
model = HPIQuestionBank
fields = ['id','label','hpianswers_set','answer_type',]
read_only_fields = ['checkboxes',]
Or you can work with SerializerMethodField as
class TemplateQuestionBankSerializer(serializers.ModelSerializer):
answer_type = serializers.CharField(allow_null = True, allow_blank=True)
checkboxes = SerializerMethodField()
def get_checkboxes(self, instance):
return CheckBoxesListField(instance).data

Access field from intermediary model in Django Rest Framework

Having a hard time trying to access a field from an intermediary model in DRF.
Let's see the related models:
class School(models.Model):
created = models.DateTimeField(auto_now_add=True)
name = models.CharField(max_length=50, verbose_name=_(u'Name'))
staff_order = models.ManyToManyField(settings.AUTH_USER_MODEL, verbose_name=_(u'Staff ordering'), through='StaffOrder', related_name='school_staff_order')
class User(AbstractUser):
phone = models.CharField(max_length=20, blank=True, null=True)
address = models.CharField(max_length=150, blank=True, null=True)
about_me = models.CharField(max_length=200, blank=True, null=True)
REQUIRED_FIELDS = ['email']
def __unicode__(self):
return u'{0}'.format(self.username)
class StaffOrder(models.Model):
user = models.ForeignKey(settings.AUTH_USER_MODEL)
school = models.ForeignKey(School)
order = models.PositiveSmallIntegerField(default=0, verbose_name=_(u'Staff ordering for this school'))
class Meta:
verbose_name_plural = _(u'Staff ordering')
Now what I'm expecting is being able to read order field from StaffOrder in when returning a QuerySet for users (StaffSerializer). Here are the Serializers:
class StaffRoleSerializer(serializers.ModelSerializer):
class Meta:
model = StaffOrder
fields = (
'order',
)
class StaffSerializer(serializers.ModelSerializer):
username = serializers.CharField(max_length=75, required=True)
email = serializers.CharField(max_length=75, required=True)
order = StaffRoleSerializer(source='school_staff_order')
class Meta:
model = User
What is returned in order for the StaffSerializer is a Manager, instead of the order field from the StaffOrder model related with this User.
A JSON expected response for Staff would be something like this:
[
{
"username": "Denise",
"email": "deniseburton#maximind.com",
"order": 9
},
{
"username": "Ina",
"email": "inaburton#maximind.com",
"order": 4
}
]
I'd love to be able to also write this value from the serializer, but I can do that in the Viewset, but I really need to read this value in the Serializer itself...any idea what I'm missing here?
First you have to understand that one user can have many staff orders. In your models you have defined it that way.
To get the json output you have specified in your question you need to query the StaffOrder objects instead of users.
class StaffOrderSerializer(serializers.ModelSerializer):
username = serializers.CharField(source='user.username')
email = serializers.CharField(source='user.email')
class Meta:
model = StaffOrder
fields = ('order', )
Use this serializer class in a list view:
class StaffOrderListApi(generics.ListAPIView):
serializer_class = StaffOrderSerializer
def get_queryset(self):
# using distinct because same user can have multiple staff orders
# based on your example it seems you want distinct users
return StaffOrder.objects.all().distinct('user')
This will get you exactly the json you want.

Django Rest Framework - How to nest several fields in a serializer?

I have several a base model with several control fields. Among them a location fields compound from lat, lon, accuracy, provider and client time. Most of my writable models (and hence resources) are inheriting from this base model.
I'm trying to make DRF serialize the location related fields in a nested "location" field. For example,
{
"id": 1,
"name": "Some name",
"location": {
"lat": 35.234234,
"lon": 35.234234,
"provider": "network",
"accuracy": 9.4,
}
}
I'ts important to remember that these fields are regular (flat) fields on the base model.
I've investigated and found several options
Create a custom field and by overriding "get_attribute" create the nested representation. I don't like this solution because i lose some of the benefits of the model serializer such as validation.
Create a nested resource called Location. I guess i could make it work by adding a property by the same name on the model but again, no validations.
So my question is, What is the best way to nest ( or group ) several fields in a DRF serializer ?
DRF 3.0.0, Django 1.7
EDIT:
Building on top of #Tom Christie answer this is what i came up with (simplified)
# models.py
class BaseModel(models.Model):
id = models.AutoField(primary_key=True)
lat = models.FloatField(blank=True, null=True)
lon = models.FloatField(blank=True, null=True)
location_time = models.DateTimeField(blank=True, null=True)
location_accuracy = models.FloatField(blank=True, null=True)
location_provider = models.CharField(max_length=50, blank=True, null=True)
#property
def location(self):
return {
'lat': self.lat,
'lon': self.lon,
'location_time': self.location_time,
'location_accuracy': self.location_accuracy,
'location_provider': self.location_provider
}
class ChildModel(BaseModel):
name = models.CharField(max_lengtg=10)
# serializers.py
class LocationSerializer(serializers.Serializer):
lat = serializers.FloatField(allow_null=True, required=False)
lon = serializers.FloatField(allow_null=True, required=False)
location_time = serializers.DateTimeField(allow_null=True, required=False)
location_accuracy = serializers.FloatField(allow_null=True, required=False)
location_provider = serializers.CharField(max_length=50,allow_null=True, required=False)
class BaseSerializer(serializers.ModelSerializer):
def create(self,validated_data):
validated_data.update(validated_data.pop('location',{}))
return super(BaseSerializer,self).create(validated_data)
def update(self, instance, validated_data):
location = LocationSerializer(data=validated_data.pop('location',{}), partial=True)
if location.is_valid():
for attr,value in location.validated_data.iteritems():
setattr(instance,attr,value)
return super(BaseSerializer,self).update(instance, validated_data)
class ChildSerializer(BaseSerializer):
location = LocationSerializer()
class meta:
model = ChildModel
fields = ('name','location',)
I've tested with valid/invalid post/patch and it worked perfectly.
Thanks.
I'd suggest simply using explicit serializer classes, and writing the fields explicitly. It's a bit more verbose, but it's simple, obvious and maintainable.
class LocationSerializer(serializers.Serializer):
lat = serializers.FloatField()
lon = serializers.FloatField()
provider = serializers.CharField(max_length=100)
accuracy = serializers.DecimalField(max_digits=3, decimal_places=1)
class FeatureSerializer(serializers.Serializer):
name = serializers.CharField(max_length=100)
location = LocationSerializer()
def create(self, validated_data):
return Feature.objects.create(
name=validated_data['name'],
lat=validated_data['location']['lat'],
lon=validated_data['location']['lat'],
provider=validated_data['location']['provider'],
accuracy=validated_data['location']['accuracy']
)
def update(self, instance, validated_data):
instance.name = validated_data['name']
instance.lat = validated_data['location']['lat']
instance.lon = validated_data['location']['lat']
instance.provider = validated_data['location']['provider']
instance.accuracy = validated_data['location']['accuracy']
instance.save()
return instance
There's a bunch of ways you could use a ModelSerializer instead, or ways to keep the create and update methods a little shorter, but it's not clear that the extra indirection you'd be giving yourself is at all worth it.
We almost always use completely explicit serializer classes for APIs that we're building.