I'm facing a problem using python2.7 with django rest-framework. When I serialize my JSON data, a field is omitted by the serializer and I don't understand why. Here is some details.
The missing field is "country". When I'm doing POST or PUT requests on /campaigns/:id
class CampaignSerializer(serializers.HyperlinkedModelSerializer):
created_by = UserFullSerializer(read_only=True)
country = CountrySerializer(read_only=True)
class Meta:
model = Campaign
fields = ('id', 'created_by', 'name', 'media', 'status', 'begin', 'end', 'country')
class CampaignFullSerializer(serializers.HyperlinkedModelSerializer):
client = ClientSerializer(read_only=True)
collection = CollectionSerializer(read_only=True)
created_by = UserFullSerializer(read_only=True)
updated_by = UserFullSerializer(read_only=True)
channels = ChannelSerializer(read_only=True, many=True)
country = CountrySerializer(read_only=True)
class Meta:
model = Campaign
fields = ('id',
'client',
'name',
'media',
'status',
'begin',
'end',
'created_at',
'created_by',
'updated_at',
'updated_by',
'collection',
'channels',
'country')
class CountrySerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = Country
fields = ('id', 'name', 'code')
class Country(models.Model):
name = models.CharField(max_length=255)
code = models.CharField(max_length=255)
class Campaign(models.Model):
name = models.CharField(max_length=255)
media = models.IntegerField(choices=constant.MEDIA_CHOICES, default=0)
status = models.IntegerField(choices=constant.STATUS_CHOICES, default=2)
created_at = models.DateTimeField(auto_now_add=True)
created_by = models.ForeignKey(User, blank=True, null=True, related_name="created_by")
updated_at = models.DateTimeField(auto_now_add=True, blank=True, null=True)
updated_by = models.ForeignKey(User, blank=True, null=True, related_name="updated_by")
client = models.ForeignKey(client.Client)
begin = models.DateField(blank=True, null=True)
end = models.DateField(blank=True, null=True)
collection = models.ForeignKey(collection.Collection, blank=True, null=True)
country = models.ForeignKey(country.Country, blank=True, null=True)
mediaplan = models.ForeignKey(mediaplan.Mediaplan, blank=True, null=True, default=None)
channels = models.ManyToManyField(channel.Channel)
When I'm doing POST on /campaign/id with the following JSON, everything works except the country field.
{
...
"channels": [],
"country": {
"id": 74,
"name": "France",
"code": "FR"
}
On the controller side when I print the request.data I got all the fields. I'm not overriding the create method of the controller.
{
...
u'country': {u'code': u'AL', u'id': 3, u'name': u'Albania'}
}
My controller looks like:
class CampaignViewSet(viewsets.ModelViewSet):
queryset = Campaign.objects.all()
serializer_class = CampaignSerializer
def create(self, request):
logger.info(request.data)
return super(CampaignViewSet, self).create(request, *args, **kwargs)
I tried to override the create method of my CountrySerializer and when I print the content of validated_data, the country field is missing in the OrderedDict..
class CountrySerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = Country
fields = ('id', 'name', 'code')
def create(self, validated_data):
logger.info(validated_data)
I'm really lost, I can't find my mistake, maybe you will. Thanks for your time.
Your CountrySerializer is read only as a nested serializer by default (per http://www.django-rest-framework.org/api-guide/relations/#nested-relationships) so you have to override the create/update method of the Campaign serializer for POST/PUT. You've tried to override it on the Country serializer instead.
Related
I'm using Django Rest Framework with DjangoFilterBackend to filter through Publications. Every publication can have multiple authors. My api call to filter the api for authors looks like:
/api/v1/publication/?author=1&author=2
This gives me every publication that either author 1 or author 2 has been assigned to. Instead I want to only see the publications that both have published. In other words it should be a logic and, not or.
My code is the following:
models.py
class Publication(models.Model):
id = models.BigAutoField(primary_key=True)
title = models.CharField(max_length=400)
author = models.ManyToManyField(Author, blank=False)
type = models.ForeignKey(
Type, on_delete=models.PROTECT, null=False, default=1)
label = models.ManyToManyField(Label, blank=True)
date = models.DateField(blank=False, null=False)
url = models.URLField(null=True, blank=True)
journal = models.CharField(max_length=400, blank=True)
bibtex = models.TextField(blank=True)
public = models.BooleanField(default=False)
updated = models.DateTimeField(auto_now=True)
created = models.DateTimeField(auto_now_add=True)
file = models.FileField(upload_to=upload_path, blank=True, null=True)
class Meta:
ordering = ['-date']
def __str__(self):
return self.title
views.py
class PublicationFilter(django_filters.FilterSet):
author = django_filters.ModelMultipleChoiceFilter(
queryset=Author.objects.all())
class Meta:
model = Publication
fields = {
'title': ["exact"],
'author': ["exact"]
}
class PublicationView(viewsets.ModelViewSet):
queryset = Publication.objects.prefetch_related(
'author', 'label').select_related('type')
serializer_class = PublicationSerializer
filter_backends = [DjangoFilterBackend,
SearchFilter, OrderingFilter]
filterset_fields = ['title', 'date', 'journal', 'label', 'author']
search_fields = ['title']
ordering_fields = ['date', 'title']
serializers.py
class PublicationSerializer(ModelSerializer):
type = TypeSerializer(read_only=False, many=False)
label = LabelSerializer(read_only=False, many=True)
author = AuthorSerializer(read_only=False, many=True)
class Meta:
model = Publication
fields = ['id', 'title', 'date',
'url', 'journal', 'label', 'author', 'type', 'date', 'bibtex', 'file']
I think if you will pass only one author field in the url query, but separate the values with a comma you will end up with a list value in the django-filter.
/api/v1/publication/?author=1,2
Than you can override filter_author method in your PublicationFilter class and build the query filter manually there.
See here under method: https://django-filter.readthedocs.io/en/stable/ref/filters.html#method
You can build the query with a help of the Q object:
How to dynamically compose an OR query filter in Django?
I have seen many tutorials about nested serializer, but unfortunately I can`t solve this task. Please, give me some tips.
I need to create this JSON
{
"external_id": "11",
"details": [
{
"amount": 7,
"price": "12.00",
"product": {
"name": "Car"
}
}
]
}
My models consist the next relative:
from django.db import models
class Order(models.Model):
NEW = 'new'
ACCEPTED = 'accepted'
FAILED = 'failed'
order_status = [
(NEW, 'new'),
(ACCEPTED, 'accepted'),
(FAILED, 'failed'),
]
status = models.CharField(max_length=12, choices=order_status, default='new', blank=False)
created_at = models.DateTimeField(auto_now_add=True)
external_id = models.CharField(max_length=128)
def __str__(self):
return f'Order № {self.external_id}'
class Product(models.Model):
name = models.CharField(max_length=64)
def __str__(self):
return self.name
class OrderDetail(models.Model):
order = models.ForeignKey(Order, on_delete=models.CASCADE,
related_name='details',
null=True, blank=True)
amount = models.IntegerField(null=True, blank=True)
product = models.ForeignKey(Product, on_delete=models.CASCADE,
related_name='product',
null=True)
price = models.DecimalField(decimal_places=2, max_digits=6, null=True, blank=True)
def __str__(self):
return f'Detail for {self.order}, detail for product {self.product}'
My view
class ProductViewSet(ModelViewSet):
queryset = Product.objects.all()
serializer_class = ProductSerializer
class OrderViewSet(ModelViewSet):
queryset = Order.objects.all()
serializer_class = OrderSerializer
pagination_class = ContentRangeHeaderPagination
class OrderDetailViewSet(ModelViewSet):
queryset = OrderDetail.objects.all()
serializer_class = OrderDetailSerializer
My serializer
class OrderDetailSerializer(serializers.ModelSerializer):
class Meta:
model = OrderDetail
fields = ['id', 'amount', 'price']
depth = 1
class ProductSerializer(serializers.ModelSerializer):
product = OrderDetailSerializer(many=True)
class Meta:
model = Product
fields = ['id', 'name', 'product']
class OrderSerializer(serializers.ModelSerializer):
details = OrderDetailSerializer(many=True)
class Meta:
model = Order
fields = ['id', 'status', 'created_at', 'external_id', 'details']
depth = 2
def create(self, validated_data): # works for first nesting
print(validated_data)
details_data = validated_data.pop('details')
request = Order.objects.create(**validated_data)
for detail_data in details_data: #
products_data = detail_data.pop('product')
request_detail = OrderDetail.objects.create(order=request, **detail_data)
for product_data in products_data:
Product.objects.create(product=request_detail, **product_data)
return request
I have errors when I try to send POST request. => KeyError 'products'
I wanted to get product fields using a loop. But I can't get this field, because I didn't identified it.
My question is: how to receive this field in OrderSerializer.
Thanks for your answers.
I cannot save multiple values for the Foreignkey field when adding instances to the database. I don't understand exactly what the problem is: in my code or in the format of the JSON object being passed.
models.py
class VendorContacts(models.Model):
contact_id = models.AutoField(primary_key=True)
vendor = models.OneToOneField('Vendors', on_delete=models.CASCADE)
contact_name = models.CharField(max_length=45, blank=True)
phone = models.CharField(max_length=45, blank=True)
email = models.CharField(max_length=80, blank=True, unique=True)
class Meta:
db_table = 'vendor_contacts'
class VendorModuleNames(models.Model):
vendor = models.OneToOneField('Vendors', on_delete=models.CASCADE, primary_key=True)
module = models.ForeignKey(Modules, models.DO_NOTHING)
timestamp = models.DateTimeField(auto_now=True)
class Meta:
db_table = 'vendor_module_names'
unique_together = (('vendor', 'module'),)
class Vendors(models.Model):
COUNTRY_CHOICES = tuple(COUNTRIES)
vendorid = models.AutoField(primary_key=True)
vendor_name = models.CharField(max_length=45, unique=True)
country = models.CharField(max_length=45, choices=COUNTRY_CHOICES)
nda = models.DateField(blank=True, null=True)
user_id = models.ForeignKey('c_users.CustomUser', on_delete=models.PROTECT)
timestamp = models.DateTimeField(auto_now_add=True)
class Meta:
db_table = 'vendors'
unique_together = (('vendorid', 'timestamp'),)
serializers.py
class VendorsSerializer(serializers.ModelSerializer):
class Meta:
model = Vendors
fields = ('vendor_name',
'country',
'nda',
'parent_vendor',)
class VendorContactSerializer(serializers.ModelSerializer):
class Meta:
model = VendorContacts
fields = (
'contact_name',
'phone',
'email',)
class VendorModulSerializer(serializers.ModelSerializer):
class Meta:
model = VendorModuleNames
fields = ('module',)
views.py
class VendorsCreateView(APIView):
"""Create new vendor instances from form"""
serializer_class = (VendorsSerializer)
def post(self, request, *args, **kwargs):
vendor_serializer = VendorsSerializer(data=request.data)
vendor_contact_serializer = VendorContactSerializer(data=request.data)
vendor_modules_serializer = VendorModulSerializer(data=request.data)
try:
vendor_serializer.is_valid(raise_exception=True) \
and vendor_contact_serializer.is_valid(raise_exception=True) \
and vendor_modules_serializer.is_valid(raise_exception=True) \
vendor = vendor_serializer.save(user_id=request.user)
vendor_contact_serializer.save(vendor=vendor)
vendor_modules_serializer.save(module= maybe something here?????, vendor=vendor)
except ValidationError:
return Response({"errors": (vendor_serializer.errors,
vendor_contact_serializer.errors,
vendor_modules_serializer.errors
)},
status=status.HTTP_400_BAD_REQUEST)
else:
return Response(request.data, status=status.HTTP_200_OK)
JSON body
{
"vendor_name": "Awrazofgsxsdsxjwszsslsasdjegdzasas",
"country": "Canada",
"module": [
1,
2
],
"NDA date": "",
"contact_name": "Tim",
"email": "teaszt#tesstd.gmail",
"phone": "+3464784594940",
"parent_vendor": "23"
}
When I send JSON, I get the response
{
"module": [
"Incorrect type. Expected pk value, received list."
]
}
Looks like I'm finally confused about multiple saving
Your ForeignKey should be set on the related class Modules.
I am using Django-rest-auth to authenticate my users, that works well. how my model is set up is that I have the custom user model for authentication and I also have a profile model that gets created with a signal whenever a user is created.
I want that when the users are fetched in its URL, the profile for that user is also displayed, and I have passed through the serializer.
THE PROBLEM: I am getting null instead of the actual data
my models.py (I didnt include some models like the user managers, skill, e.t.c as i felt they werent relevant)
class User(AbstractBaseUser, PermissionsMixin):
username = None
email = models.EmailField(max_length=254, unique=True)
fullname = models.CharField(max_length=250)
is_staff = models.BooleanField(default=False)
is_superuser = models.BooleanField(default=False)
is_active = models.BooleanField(default=True)
last_login = models.DateTimeField(null=True, blank=True)
date_joined = models.DateTimeField(auto_now_add=True)
USERNAME_FIELD = 'email'
REQUIRED_FIELDS = ['fullname']
objects = UserManager()
class Profile(models.Model):
user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE, related_name='profiles')
date_of_birth = models.DateField(blank=True, verbose_name="DOB", null=True)
bio = models.TextField(max_length=500, blank=True, null=True)
profile_photo = models.CharField(blank=True, max_length=300, null=True)
skills = models.ManyToManyField(Skill, related_name='skills')
sex = models.CharField(max_length=1, choices=SEX, blank=True, null=True)
type_of_body = models.CharField(max_length=8, choices=BODYTYPE, blank=True, null=True)
feet = models.PositiveIntegerField(blank=True, null=True)
inches = models.PositiveIntegerField(blank=True, null=True)
lives_in = models.CharField(max_length=50, blank=True, null=True)
updated_on = models.DateTimeField(auto_now_add=True)
the serializers.py code
class ProfileSerializer(serializers.ModelSerializer):
class Meta:
model = Profile
fields = "__all__"
read_only_fields = ('pk',)
class CustomUserDetailsSerializer(serializers.ModelSerializer):
profiles = ProfileSerializer(read_only=True)
class Meta:
model = User
fields = ('pk', 'email', 'fullname', 'profiles')
read_only_fields = ('email', 'fullname', 'profiles')
view.py
class ListUsersView(APIView):
permission_classes = [AllowAny]
def get(self, request):
user = User.objects.all()
serializer = CustomUserDetailsSerializer(user, many=True)
return Response(serializer.data)
urls.py
url(r'^list-users/$', ListUsersView.as_view(), name='list-users'),
the JSON response I get
[
{
"pk": 1,
"email": "opeyemiodedeyi#gmail.com",
"fullname": "opeyemi odedeyi",
"profiles": {
"date_of_birth": null,
"bio": null,
"profile_photo": null,
"sex": null,
"type_of_body": null,
"feet": null,
"inches": null,
"lives_in": null
}
}
]
how do I get the profiles to show in the response?
I guess the problem is in your CustomUserDetailsSerializer. you have one-to-many relationship with User and Profile, but you are not explicitly telling it in your profiles attribute of the serializer. You have to pass many=True argument to the ProfileSerializer:
class CustomUserDetailsSerializer(serializers.ModelSerializer):
profiles = ProfileSerializer(many=True, read_only=True)
class Meta:
model = User
fields = ('pk', 'email', 'fullname', 'profiles')
read_only_fields = ('email', 'fullname', 'profiles')
But I am curious why you are using one-to-many relationship for that. You could use OneToOneField which explicitly tells that one user can only have one profile. But I am not familiar with your situation, so that is only my advice.
I am trying to implement simple api in Django Rest Framework.
I have following models in models.py:
class Entry(BaseModel):
company_name = models.CharField(max_length=256, null=True, blank=True)
first_name = models.CharField(null=True, default=None, max_length=32)
last_name = models.CharField(null=True, default=None, max_length=32)
code = models.CharField(null=True, default=None, max_length=12)
class Meta:
db_table = 'entry'
class Admin(admin.ModelAdmin):
list_display = ('company_name', 'code')
list_display_links = ('company_name', )
ordering = ('-created',)
class EntryContactData(BaseModel):
entry = models.ForeignKey(Entry, related_name='contact')
email = models.CharField(max_length=256, null=True, blank=True)
website = models.CharField(max_length=64, null=True, blank=True)
phone = models.CharField(max_length=64, null=True, blank=True)
My API serializers.py:
from django.contrib.auth.models import User, Group
from rest_framework import serializers
from core.models import Entry, EntryContactData
class EntryContactSerializer(serializers.ModelSerializer):
class Meta:
model = EntryContactData
fields = ('uuid', 'email', 'website', 'phone')
class EntrySerializer(serializers.ModelSerializer):
contact = EntryContactSerializer(many=False, read_only=True)
class Meta:
model = Entry
fields = ('uuid', 'company_name', 'first_name', 'last_name', 'contact')
And my API views:
from core.models import Entry
from .serializers import EntrySerializer
class EntryViewSet(viewsets.ViewSet):
"""
A simple ViewSet for listing or retrieving users.
"""
queryset = Entry.objects.all()
def retrieve(self, request, pk=None):
queryset = Entry.objects.all()
entry = get_object_or_404(queryset, code=pk)
serializer = EntrySerializer(entry, context={'request': request})
return Response(serializer.data)
When I want to retrieve single entry its contact field is empty:
{
"uuid": "e6818508-a172-44e1-b927-3c087d2f9773",
"company_name": "COMPANY NAME",
"first_name": "FIRSTNAME",
"last_name": "LASTTNAME",
"contact": {}
}
So it doesn't contain any of fields defined in EntryContactSerializer
What am I doing wrong? How can I force it to return all fields included in serializer? Thank you guys.
Try setting many=True in EntrySerializer, and provide a source attribute to the serializer,
class EntrySerializer(serializers.ModelSerializer):
contact = EntryContactSerializer(source='contact', many=True, read_only=True)
class Meta:
model = Entry
fields = ('uuid', 'company_name', 'first_name', 'last_name', 'contact')