Django REST Framework nested relations without related names - django

I have some models that relates to User, but does not have a related name on user:
class Registration(models.Model):
user = models.OneToOneField('auth.User', related_name='+')
class ManyToOneModel(models.Model):
user = models.ForeignKey('auth.User', related_name='+')
I would like to make a serializer for User, which can have this as a nested resource. Is there a way to specify what the queryset/object is? This is an example of what I have - and it completly expectedly failes with 'User' object has no attribute 'registration':
class UserSerializer(serializers.Serializer):
pk = serializers.Field()
registration = RegistrationSerializer()
many_to_one_model = ManyToOneModelSerializer(many=True, required=False)

I guess you'd need to manually query for the related objects and then construct the serializers by hand. You'd then construct the final representation and pass that as the data parameter to a Response object.
It seems like you're making life difficult though. If you just define the related_name on your related models you could use ModelSerializer (or HyperlinkedModelSerializer) and it would all Just Work™. — Is there some reason why you can't do this?

Related

Django (DRF) ManyToMany field choices / limit

Working with Django REST Framework I am wondering if it's possible to limit the choices / options of a ManyToMany field on a model to a specific QuerySet?
Using the models below (scroll down to see models), I am curious if it's possible to define this limit in the definition of the model, to achieve the following:
# Having the following Employee instance
emp = Employee(...)
# Should return only the instances with value 'case' in EmployeeSubstitute.type field
emp.substitute_case.all()
# Should return only the instances with value 'phone' in EmployeeSubstitute.type field
emp.substitute_phone.all()
Models:
class Employee(models.Model):
substitute_case = models.ManyToMany(through=EmployeeSubstitute, ...)
substitute_phone = models.ManyToMany(through=EmployeeSubstitute, ...)
class EmployeeSubstitute(models.Model):
from = models.ForeignKey(Employee, ...)
to = models.ForeignKey(Employee, ...)
type = models.CharField(choices=..., ...) # choose between type 'case' and 'phone'
I see that there's the limit_choices_to parameter, but that's not what I am looking for, since that only effects the options shown when using a ModelForm or the admin.
Well, ManyToManyField returns related objects and as docs state
By default, Django uses an instance of the Model._base_manager manager
class when accessing related objects (i.e. choice.question), not the
_default_manager on the related object. This is because Django needs to be able to retrieve the related object, even if it would otherwise
be filtered out (and hence be inaccessible) by the default manager.
If the normal base manager class (django.db.models.Manager) isn’t
appropriate for your circumstances, you can tell Django which class to
use by setting Meta.base_manager_name.
Base managers aren’t used when querying on related models, or when
accessing a one-to-many or many-to-many relationship. For example, if
the Question model from the tutorial had a deleted field and a base
manager that filters out instances with deleted=True, a queryset like
Choice.objects.filter(question__name__startswith='What') would include
choices related to deleted questions.
So if I read it correctly, no, it's not possible.
When you do queries and have through in your ManyToManyField, Django complains you should run these queries on your through model, rather than the "parent". I can't find it in the docs but I remember seeing it a few times.
substitute_case and substitute_phone is something that belongs to substitute and it is it's type. So just do that instead of creating those columns in Employee.
from django.db import models
class SubstituteTypes(models.TextChoices):
case = "case", "case"
phone = "phone", "phone"
class EmployeeSubstituteQueryset(models.QuerySet):
def from_employee(self, e):
return self.filter(_from=e)
def case(self):
return self.filter(type=SubstituteTypes.case)
def phone(self):
return self.filter(type=SubstituteTypes.phone)
class Employee(models.Model):
substitute = models.ManyToManyField(through='EmployeeSubstitute', to='self')
class EmployeeSubstitute(models.Model):
_from = models.ForeignKey(Employee, on_delete=models.CASCADE, related_name='a')
to = models.ForeignKey(Employee, on_delete=models.PROTECT, related_name='b')
type = models.CharField(choices=SubstituteTypes.choices, max_length=5, db_index=True)
objects = EmployeeSubstituteQueryset.as_manager()
Then, once you get your emp object (or only its id), you can do
EmployeeSubstitute.objects.from_employee(emp).case().all()
which is designed in Django philosophy.

Trouble overriding save method on Django model with ManyToManyField

I'm having trouble overriding the save method on a Django model to check a restriction on a many-to-many field.
Say I have the following models:
class Person(models.Model):
name = models.CharField()
class ClothingItem(models.Model):
description = models.CharField()
owner = models.ForeignKey(Person)
class Outfit(models.Model):
name = models.CharField()
owner = models.ForeignKey(Person)
clothing_items = models.ManyToManyField(ClothingItem)
I would like to put a restriction on the save method of Outfit that ensures that each ClothingItem in a given outfit has the same owner as the Outfit itself.
I.e. I'd like to write:
class Outfit(models.Model):
name = models.CharField()
owner = models.ForeignKey(Person)
clothing_items = models.ManyToManyField(ClothingItem)
def save(self, *args, **kwargs):
for ci in self.clothing_items:
if ci.owner != self.owner:
raise ValueError('You can only put your own items in an outfit!)
super(Outfit, self).save(*args, **kwargs)
but when I try that I get an error about <Outfit: SundayBest>" needs to have a value for field "outfit" before this many-to-many relationship can be used.
Any ideas what's going wrong here?
There are two issues going on here. To directly answer your question, the error basically means: You cannot refer to any m2m relationship if the original object(an instance of Outfit here) is not saved in database.
Sounds like you are trying to do the validation in save() method, which is a pretty bad practice in django. The verification process should typically happen in Form that creates Outfit objects. To override default django form, please refer to django ModelAdmin.form. To understand how to do validation on django forms, check ModelForm validation.
If you want code to refer to for m2m validation, I found a good example from SO.

Saving django model with many to many relationship to database in django rest framework

I need to be able to do a post on an api endpoint to save an adgroup model.The model has a many to many field. I know I need to overwrite the create() method.But How is where I am stuck at . The incoming request data will have the id for the other model (creative). This id will already be present in the creative table.
Django creates another table called adgroup_creative to hold this M2M relationship.I need to populate that table when saving this adgroup object.
class AdGroup(models.Model):
adgroup_name = models.CharField(max_length=200, verbose_name="Name")
creative = models.ManyToManyField(Creative, verbose_name="Creative")
class Creative(models.Model):
creative_name= models.CharField(max_length=200, verbose_name="Name", default=0)
ad_type= models.PositiveIntegerField(max_length=1,verbose_name="Ad Type")
class AdGroupSerializer(serializers.ModelSerializer):
class Meta:
model = AdGroup
fields = ('id','adgroup_name','creative')
class CreativeSerializer(serializers.ModelSerializer):
class Meta:
model = Creative
fields = ('id','creative_name')
class AdGroupViewSet(mixins.CreateModelMixin, mixins.RetrieveModelMixin, mixins.UpdateModelMixin, mixins.ListModelMixin, viewsets.GenericViewSet):
queryset = AdGroup.objects.all().order_by('-id')
serializer_class = AdGroupSerializer
https://codereview.stackexchange.com/questions/46160/django-rest-framework-add-remove-to-a-list
Save a many-to-many model in Django/REST?
You should have a look at the serializer relation documentation.
You don't need anything special if you simply use ID to represent a M2M relation with DRF. You'll need to override the create/update methods only if you intend to provide non existing related objects or use nested serializers.
In the current case, you don't need nested serializers because you want to provide related instances' IDs.

Django get all, with related models

Problem:
I'm using Django Rest Framework and i want to fetch all models with the relationships included, like this:
TestModel.objects.all()
My model looks like this:
class TestModel(models.Model):
name = models.CharField(max_length=32)
related_model = models.ForeignKey(TestRelation)
Problem is, i only get the Primary Keys for related_model but i need the whole related_model!
I'm using the ListCreateAPIView, with the above queryset (TestModel.objects.all()) and the most basic form of the ModelSerializer.
I tried the PrimaryKeyRelatedField but i get the same result..
Thanks!
Just create serializer for your related model:
class TestRelationSerializer(serializers.ModelSerializer):
class Meta:
meta = TestRelation
and use is as field in TestModelSerializer:
class TestModelSerializer(serializers.ModelSerializer):
related_model = TestRelationSerializer()
You can also do it other way around, by using TestModelSerializer as field in TestRelationSerializer with many set to true:
class TestRelationSerializer(serializers.ModelSerializer):
testmodel_set = TestModelSerializer(many=True)
just remember, you can't do both at once due to infinite recursion it makes.

Display serializer field as 'named' field instead of a primary key field

I am working on a Django REST framework API and are running into some issues with using the Serializers. I'll try to isolate the problem a little. Basically there are the Room and a Location models.
class Room(models.Model):
uuid = UUIDField(primary_key=True)
...
location = models.ForeignKey(Location, related_name='room')
...
class Location(models.Model):
uuid = UUIDField(primary_key=True)
name = models.CharField(max_length=255)
def __unicode__(self):
return unicode(self.name)
and the corresponding serializers,
class RoomSerializer(serializers.ModelSerializer):
location = serializers.RelatedField()
class Meta:
model = Room
fields = ('uuid', 'location')
class LocationSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = Location
fields = ('uuid', 'name')
I've explored the many ways to change the display of uuid to the location name including the use of serializers.SerializerMethodField() and also using the above serializers.RelatedField(), and they do work. As a reference, see the JSON output below.
{
"uuid": "491ab09d-qqqq-wwww-eeee-5801dbac0fef",
"location": "Kota Kinabalu",
"created": "2014-09-03T07:52:45.399Z",
"modified": "2014-09-03T07:52:45.530Z"
}
However, I am not able to do a model.save() on Room since Django or rather the database (postgresql in my case) complains that the location_id is empty. I think its due to the fact that when I send a POST request to the API endpoint /api/rooms/ the location_id is left blank when I modify my RoomSerializer.
If one uses a default ModelViewSet, you would see that the Location field is missing from the default form.
How do I use serializers properly such that I could display the name of the location ("Kota Kinabalu" instead of a uuid string like 6e6acbbb-xxxx-yyyy-zzzz-1cf1a5bac22c) and I am still able to accept a correct POST request?
(I'm moving quik_silv comment into an answer)
You should use the SlugRelatedField provided by DRF: http://www.django-rest-framework.org/api-guide/relations/#slugrelatedfield
In this case all you have to do is add a field like: location = serializers.SlugRela‌​tedField(slug_field='‌​name', read_only=True). Note the use of the parameter read_only, which should fix the problem in creating model instances.
You should try using customized UUID related field. You can find an example here.
I know it had been a long time and you might have already got the solution. But I am posting here the solution how I implemented this. After reading a bit of docs, I came across a method named to_representation(). I used this method to convert the data which is being returned in form of primary key to any other form. You can refer about this method in the docs here.