I am new to this django-rest-framework, my problem is how am I going to insert a data with one-to-one relationship in the views.py.
I can do the normal way of inserting data like the one below,
def post(self, request,format=None):
p = Person()
p.firstname = "John"
p.middlename = "K"
p.lastname = "Rambo"
p.save()
e = Employee()
e.code = "emp-002"
e.person_id = p.id
e.save()
I cannot do the approach above because I want to learn more about DRF.
To give you more idea please see below :
I have this model
class Person(models.Model):
firstname = models.CharField(max_length=150, default="")
middlename = models.CharField(max_length=150, default="")
lastname = models.CharField(max_length=150, default="")
class Employee(models.Model):
person = models.OneToOneField(Person, on_delete=models.CASCADE,primary_key=True)
code = models.CharField(max_length=100, default="")
I have this serializer
class PersonSer(serializers.ModelSerializer):
class Meta:
model = Person
fields = (
'id',
'firstname',
'middlename',
'lastname',)
class EmployeeSer(serializers.ModelSerializer):
person = PersonSer()
class Meta:
model = Employee
fields = ('code','person')
I have this view
class SaveData(APIView):
#transaction.atomic
def post(self,request, format=None):
p = PersonSer(data=request.data)
if p.is_valid():
p.save()
request.data['person'] = p.data['id']
request.data['code'] = "Emp-"+str(p.data['id'])
"""
I expect request.data now equal to
{
'person' : 102, # we assume the newly created person id == 102
'code' : 'Emp-102',
'firstname' : 'John',
'middlename' : 'K.',
'lastname' : 'Rambo'
}
"""
es = EmployeeSer(data=request.data)
if es.is_valid():
es.save()
else:
print(es.errors) # {'person': [ErrorDetail(string='This field is required.', code='required')]}
"""
I also tried this,
es = EmployeeSer(data={'person' : p.data, 'code' : 'Sample code'})
if es.is_valid():
es.save()
else:
print(es.errors) #{'person': {'fullname': [ErrorDetail(string='This field may not be blank.',code='blank')], ]}}
"""
return Response({'message':'okay'})
The request.data is a formData, the value is
{
'firstname' : 'John',
'middlename' : 'K.',
'lastname' : 'Rambo'
}
You can override the create method of the PersonSer to achieve this.
class PersonSer(serializers.ModelSerializer):
...
def create(self, validated_data):
person = Person.objects.create(**validated_data)
Employee.objects.create(person=person, code=f"Emp-{person.id}")
return person
And you can simplify your view to:
...
#transaction.atomic
def post(self,request, format=None):
p = PersonSer(data=request.data)
p.is_valid(raise_exception=True)
p.save()
return Response({'message':'okay'})
...
raise_expection=True will cause serializer to raise ValidationError exception if there are validation errors.
Related
I am new to Django Rest Framework and django itself. I want to post a request wherein a key has a list of objects. So far, I have created the User_parameter model and I am passing it as a foreign key. I am lost and do not know what to do from here on. Do I need to override create() in the User_Parameters_Serializerserializer or something entirely different?
I have added code for my models.py, serializer.py, the function-based view where I am handling the post request and my desired post request outline.
Thank you for your time. I really appreciate it.
# The post request I want to send looks like this:
{
"model_path": "some path",
"data_path": "some path for csv",
"sep": ",",
"target_column": "target_column",
"label_column": "lable_column",
"function_names": "some strings",
"user_params": [{
"standard_deviation": between[1-3],
"direction": 0 OR 1,
"feature_list": "some strings"
}]
}
#models.py
class User_Parameters(models.Model):
def validate_standard_deviation_number(value):
if value not in range(1,4):
raise ValidationError(
_('%(value)s is not in range: [1-3]'),
params={'standard deviation': value},
)
def validate_direction(value):
#### 0 - Negative
#### 1 - Positive
if value not in range(0,2):
raise ValidationError(
_('%(value)s needs to be either 0 (Negative) or 1 (Positive)'),
params={'direction': value},
)
standard_deviation = models.IntegerField(default=1, validators=[validate_standard_deviation_number])
direction = models.IntegerField(default=1, validators=[validate_direction])
feature_list = models.CharField(default='', max_length=3000)
def __str__(self):
return "std dev: %s, dir: %s, feature list: %s" % (self.standard_deviation, self.direction, self.feature_list)
class Machine_Learning_Test_Engine(models.Model):
model_path = models.CharField(default='', max_length = 3000)
data_path = models.CharField(default='', max_length = 3000)
sep = models.CharField(default='', max_length = 3000)
target_column = models.CharField(default='', max_length = 3000)
label_column = models.CharField(default='', max_length = 3000)
function_names = models.CharField(default='', max_length=3000)
user_params = models.ForeignKey(User_Parameters, on_delete=models.CASCADE)
#serializer.py
class User_Parameters_Serializer(serializers.ModelSerializer):
class Meta:
model = User_Parameters
fields = '__all__'
class MLTE_Serializers_Model(serializers.ModelSerializer):
class Meta:
model = Machine_Learning_Test_Engine
fields = '__all__'
#api_view(['POST'])
def mlte_reqeust(request):
if request.method == 'POST':
serializer = MLTE_Serializers_Model(data = request.data)
if serializer.is_valid():
serializer.save()
global mlte
mlte = MLTE(serializer.data['model_path'], serializer.data['data_path'],serializer.data['sep'], serializer.data['target_column'],serializer.data['label_column'])
functions = str(serializer.data['function_names']).split(",")
### I have to pass the ```{"user_param":[{A1}]}``` as the argument of ```generate_data()```.
mlte.generate_data(functions)
json_data_pandas = pandas.DataFrame.to_json(mlte.data.head())
return Response(json_data_pandas, status=status.HTTP_200_OK)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
I have these serializers
class OwnerSerializer(serializers.ModelSerializer):
class Meta:
model = User
fields = [
'id',
'name'
]
class PetSerializer(serializers.ModelSerializer):
owner = OwnerSerializer(many=False, read_only=False)
class Meta:
model = Pet
fields = [
'id',
'name',
'owner'
]
def create(self, validated_data):
"""
Create and return a new `Pet` instance, given the validated data.
"""
owner_data = validated_data.pop('owner', None)
if owner_data:
owner = User.objects.get(**owner_data)
validated_data['owner'] = owner
return Pet.objects.create(**validated_data)
This is my ViewSet:
class PetViewSet(viewsets.ModelViewSet):
"""
ModelViewSet for model pets
"""
queryset = Pet.objects.all()
serializer_class = PetSerializer
def create(self, request, *args, **kwargs):
request.data['owner'] = {'id': request.user.id, 'name': request.user.name}
pet_serializer = self.get_serializer(data=request.data)
pet_serializer.is_valid(raise_exception=True)
self.perform_create(pet_serializer)
headers = self.get_success_headers(pet_serializer.data)
return Response(pet_serializer.data, status=status.HTTP_201_CREATED, headers=headers)
But when storing it, all the data is saved, but in the created function the owner object does not arrive, it is shown as 'owner': OrderedDict ()}
So when I return the saved object, I get it with the owner with the default id, although the user I get in the view is 'owner': {'id': 10, 'name': 'My name'}} ):
My Json Response:
{
"id": 26,
"name": "My pet",
"owner": {
"id": 1
}
}
What am I doing wrong? Thank you
It looks to me like it's returning normalized data of the owner object, rather than the relation.
Instead of owner = OwnerSerializer() try something like:
owner = PrimaryKeyRelatedField(queryset=Owner.objects.all())
I made the next changes in my Serializers,the main changes was in the OwnerSerializer, 'cause the id was read only for default.
class OwnerSerializer(serializers.ModelSerializer):
class Meta:
model = User
fields = [
'id',
'name'
]
extra_kwargs = {
'id': {
'read_only': False,
'required': True
}
}
class PetSerializer(serializers.ModelSerializer):
owner = OwnerSerializer(many=False, read_only=False)
class Meta:
model = Pet
fields = [
'id',
'name',
'owner'
]
def create(self, validated_data):
"""
Create and return a new `Pet` instance, given the validated data.
"""
owner_data = validated_data.pop('owner', None)
if owner_data:
owner = User.objects.get(**owner_data)
validated_data['owner'] = owner
return Pet.objects.create(**validated_data)
.get returns one item and if it matched more, It raises an expception.
owner = User.objects.get(**owner_data), remove the trailing [0]
EDIT: Since this was not your problem, I'll tell you the problem
You're overriding create which have the validated_data, You assumed that the owner is validated but it may wrong.
Do the same logic in a method named validate_owner(self, value) in which value is the value you passed for owner in the POST before it gets validated.
(Question before: Django Rest Framework nested relationship)
I've made serializer like this:
serializers.py
from rest_framework import serializers, fields
from .models import Pegawai,Barang
class BarangSerializer(serializers.ModelSerializer):
class Meta:
model = Barang
fields = (
'pegawai',
'nama_barang',
'harga_barang',
)
def to_representation(self, instance):
rep = super().to_representation(instance)
rep['pegawai'] = instance.pegawai.name
return rep
class PegawaiSerializer(serializers.ModelSerializer):
barangs = BarangSerializer(read_only=True, many=True)
class Meta:
model = Pegawai
fields = (
'id',
'name',
'alias',
'barangs',
)
Results :
{
"pegawai": "Ryan",
"nama_barang": "burjo",
"harga_barang": "1234"
},
And How to make the result like this in the barang API when posted the data:
{
"pegawai": {"id" : 1,
"name" : "Ryan",
"alias" : "R"}
"nama_barang": "burjo",
"harga_barang": "1234"
},
Please help, cheers.
Write extra serializer and wire-up it in to_representation(..) method,
class PegawaiShortSerializer(serializers.ModelSerializer):
class Meta:
model = Pegawai
fields = (
'id',
'name',
'alias',
)
class BarangSerializer(serializers.ModelSerializer):
class Meta:
model = Barang
fields = (
'pegawai',
'nama_barang',
'harga_barang',
)
def to_representation(self, instance):
rep = super().to_representation(instance)
rep['pegawai'] = PegawaiShortSerializer(instance.pegawai).data
return rep
Have you try:
rep = super().to_representation(instance)
pegawai_obj = instance.pegawai
pegawai_data = {"id":pegawai_obj.id, "name":pegawai_obj.name, "alias":pegawai_obj.alias}
rep['pegawai'] = pegawai_data
return rep
But I don't think this is the best solution.
I'm looking for a way to query data from models such as Author with fields [pk, name] and Book [pk, author, title, published] so, in the end, I'd have got something like that
[
{
'pk': 'author_pk_1', 'name': 'author__name_1', 'books': [
{
'pk': 'book__pk_1',
'title': 'book_name_1',
'published': 'book_published_1',
},
{
'pk': 'book__pk_2',
'title': 'book_name_2',
'published': 'book_published_2',
}
]
}
]
At the moment I achieved this by iterating through QuerySet and manually grouping them, but it does not look good at all:
authors = Author.objects.values('pk', 'name')
grouped = list()
for author in authors:
entry = dict()
entry['pk'] = author['pk']
entry['name'] = author['name']
entry['books'] = list(Book.objects.filter(author=author['pk']).values('pk', 'title', 'published'))
grouped.append(entry)
UPDATE:
Well, it was easier than I thought; here what I've got so far:
view.py
#api_view(['GET'])
def api_get_grouped_books(request, *args, **kwargs):
if request.method == 'GET':
authors = Author.objects.all()
serializer = AuthorSerializer(authors, many=True)
return Response(serializer.data)
serializers.py
class BookSerializer(serializers.ModelSerializer):
class Meta:
model = Book
fields = ('title', 'published', )
class AuthorSerializer(serializers.ModelSerializer):
books = BookSerializer(many=True)
class Meta:
model = Author
fields = ('name', 'books', )
def create(self, validated_data):
books_data = validated_data.pop('books')
author, _ = Author.objects.get_or_create(
name=validated_data.get('name'),
defaults=validated_data
)
for book_data in books_data:
try:
Book.objects.get(title=book_data.get('title'))
except ObjectDoesNotExist:
Book.objects.create(author=author, **book_data)
return author
Note: At first, I was getting an AttributeError when I tried to post some data - it was complaining that the field books not part of Author model, so I ended up adding the attribute related_name='books' to author ForeignKey field in Book model.
I'm trying to map a foreign key to POST data when creating a new object through a serializer. There are two foreign keys in the object, one is serializing perfectly, the other is creating an error.
Model:
class Event(models.Model):
owner = models.ForeignKey('auth.User', related_name='owner', blank=True)
date = models.DateField('eventdate')
time = models.TimeField('eventtime', default=now)
eventtype = models.ForeignKey(EventType, related_name='eventtype', blank=True)
# duration = models.DurationField()
location = models.CharField(max_length=200, blank=True)
attenders = models.ManyToManyField(User, related_name='attenders')
invited = models.ManyToManyField(User, related_name='invitedlist')
View:
class EventMixin(RetrieveUpdateDestroyAPIView, CreateAPIView):
serializer_class = EventSerializer
def get_queryset(self):
return Event.objects.all()
def partial_update(self, request, *args, **kwargs):
request['owner'] = request.user
sname = request['eventtype']
request['eventtype'] = EventType.objects.filter(sname=sname)
json_str = json.dumps(self.request.data)
data = json.loads(json_str)
try:
invited = list(data['toInvite'])
for i in invited:
for j in User.objects.filter(username=i):
invite = EventInvite(invited=j, sender=request.user, event=self.get_object())
invite.save()
self.get_object().invited.add()
except KeyError:
pass
return super(EventMixin, self).partial_update(request, *args, **kwargs)
def create(self, request, *args, **kwargs):
new = {}
new['owner'] = request.user.__dict__
new['date'] = request.data['date']
new['time'] = request.data['time']
new['location'] = request.data['location']
sname = request.data['eventtype']
new['eventtype'] = EventType.objects.get(sname=sname).__dict__
json_str = json.dumps(self.request.data)
data = json.loads(json_str)
serializer = EventMixinSerializer(data=new)
with open('/tmp/log.txt', 'w+') as f:
f.write(str(serializer.is_valid()))
f.write(str(serializer.validated_data))
f.close()
serializer.is_valid();
serializer.save()
try:
invited = list(data['toInvite'])
for i in invited:
for j in User.objects.filter(username=i):
invite = EventInvite(invited=j, sender=request.user, event=self.get_object())
invite.save()
self.get_object().invited.add()
except KeyError:
pass
Serializer:
class EventMixinSerializer(serializers.ModelSerializer):
owner = UserSerializer(read_only=True)
eventtype = EventTypeSerializer()
attenders = FriendsListingField(read_only=True)
invited = FriendsListingField(read_only=True)
class Meta:
model = Event
fields = ('owner', 'eventtype', 'date', 'time', 'location', 'id', 'attenders', 'invited')
def create(self, validated_data):
owner = validated_data.pop('owner')
owner = owner.instance
eventtype = validated_data.pop('eventtype')
eventtype = eventtype.instance
event = Event.objects.create(owner=owner, eventtype=eventtype, **validated_data)
event.save()
return event
Error when owner field present:
False
{'owner': OrderedDict([('username', ['A user with that username already exists.'])])}
Result when UserSerializer(read_only=True) (pretty much diabling it):
True
OrderedDict([('eventtype', OrderedDict([('lname', 'Swimming'), ('sname', 'SWM'), ('category', '1')])), ('date', datetime.date(2015, 12, 22)), ('$
(Notice the event type data in the result)
Thanks!
You need to remove the validators from UserSerializer.
Assuming UserSerializer is a User ModelSerializer it'll extract the unique constraint on the User.username from the Model and your validation will fail.
To work this around you'll need to remove the UniqueValidator by overriding the validators list for the username field of the UserSerializer