Python DRF PrimaryKeyRelatedField to use uuid instead of PK - python

I'm writing a Django REST Framework API.
My models have default Django PK for internal use AND uuid field for external reference.
class BaseModel(models.Model):
uuid = models.UUIDField(default=uuid.uuid4, editable=False)
class Event(BaseModel):
title = models.TextField()
location = models.ForeignKey('Location', null=True, on_delete=models.SET_NULL)
class Location(BaseModel):
latitude = models.FloatField()
longitude = models.FloatField()
And my serializers:
class BaseSerializer(serializers.ModelSerializer):
default_fields = ('uuid',)
class EventSerializer(BaseSerializer):
class Meta:
model = Event
lookup_field = 'uuid' # This does not work
fields = BaseSerializer.default_fields + ('title', 'location',)
class LocationSerializer(BaseSerializer):
class Meta:
model = Location
lookup_field = 'uuid' # This does not work
fields = BaseSerializer.default_fields + ('latitude', 'longitude',)
This works fine, here is what I got when I retrieve an Event:
{
"uuid": "ef33db27-e98b-4c26-8817-9784dfd546c6",
"title": "UCI Worldcup #1 Salzburg",
"location": 1 # Note here I have the PK, not UUID
}
But what I would like is:
{
"uuid": "ef33db27-e98b-4c26-8817-9784dfd546c6",
"title": "UCI Worldcup #1 Salzburg",
"location": "2454abe7-7cde-4bcb-bf6d-aaff91c107bf" # I want UUID here
}
And of course I want this behavior to work for all my ForeignKeys and ManyToMany fields.
Is there a way to customize the field used by DRF for nested models ?
Thanks !

from django.utils.translation import ugettext_lazy as _
from django.core.exceptions import ObjectDoesNotExist
from rest_framework.relations import RelatedField
from django.utils.encoding import smart_text
class UUIDRelatedField(RelatedField):
"""
A read-write field that represents the target of the relationship
by a unique 'slug' attribute.
"""
default_error_messages = {
'does_not_exist': _('Object with {uuid_field}={value} does not exist.'),
'invalid': _('Invalid value.'),
}
def __init__(self, uuid_field=None, **kwargs):
assert uuid_field is not None, 'The `uuid_field` argument is required.'
self.uuid_field = uuid_field
super().__init__(**kwargs)
def to_internal_value(self, data):
try:
return self.get_queryset().get(**{self.uuid_field: data})
except ObjectDoesNotExist:
self.fail('does_not_exist', uuid_field=self.uuid_field, value=smart_text(data))
except (TypeError, ValueError):
self.fail('invalid')
def to_representation(self, obj):
return getattr(obj, self.uuid_field)
Sample Usage:
class ProductSerializer(serializers.ModelSerializer):
category = UUIDRelatedField(
queryset=Category.objects.all(),
uuid_field='alias'
)
class Meta:
model = Product
fields = (
'id',
'alias',
'name',
'category',
)
read_only_fields = (
'id',
'alias',
)
Note that as of Django version 4, smart_text and ugettext_lazy were removed, use smart_str and gettext_lazy instead of them:
from django.utils.encoding import gettext_lazy
from django.utils.encoding import smart_str

A friend of mine send me this solution:
It works with all my related objects.
from rest_framework import serializers
from rest_framework.relations import SlugRelatedField
class UuidRelatedField(SlugRelatedField):
def __init__(self, slug_field=None, **kwargs):
slug_field = 'uuid'
super().__init__(slug_field, **kwargs)
class BaseSerializer(serializers.ModelSerializer):
default_fields = ('uuid',)
serializer_related_field = UuidRelatedField
class Meta:
pass

For nested model fields you can use the source argument in a serializer like this
class EventSerializer(BaseSerializer):
location = serializers.CharField(source='location.uuid')
class Meta:
model = Event
lookup_field = 'uuid' # This does not work
fields = BaseSerializer.default_fields + ('title', 'location',)

Related

how to create model instance in drf serializers

I am new to DRF. I want to get saved the model.
In models.py, PackageDetails and PhysicalDetail have foreignkey relationship to Member
My serializers.py is as follows:
from rest_framework import serializers
from .models import Member, PackageDetails, PhysicalDetail
class PackageDetailsSerializer(serializers.ModelSerializer):
is_expired = serializers.SerializerMethodField()
members_expiry_date = serializers.SerializerMethodField()
class Meta:
model = PackageDetails
exclude = ['id']
extra_fields = ['is_expired', 'members_expiry_date']
def get_is_expired(self, instance):
return instance.is_expired
def get_members_expiry_date(self, instance):
return instance.members_expiry_date
class PhysicalDetailSerializer(serializers.ModelSerializer):
class Meta:
model = PhysicalDetail
exclude = ['id']
class MemberSerializer(serializers.ModelSerializer):
physical_details = PhysicalDetailSerializer(many=True)
package_details = PackageDetailsSerializer(many=True)
class Meta:
model = Member
fields = '__all__'
extra_fields = ['physical_details', 'package_details']
def create(self, validated_data):
physical_detail_data = validated_data.pop("physical_details")
package_detail_data = validated_data.pop("package_details")
member = Member.objects.create(**validated_data)
PhysicalDetail.objects.create(member=member, **physical_detail_data)
PackageDetails.objects.create(member=member, **package_detail_data)
return member
views.py :
class MemberViewset(viewsets.ModelViewSet):
queryset = Member.objects.all()
serializer_class = MemberSerializer
class PackageDetailViewset(viewsets.ModelViewSet):
queryset = PackageDetails.objects.all()
serializer_class = PackageDetailsSerializer
class PhysicalDetailViewset(viewsets.ModelViewSet):
queryset = PhysicalDetail.objects.all()
serializer_class = PhysicalDetailSerializer
In GET request it worked well.. but in POST request with the same json format it responses the following:
{
"physical_details": [
"This field is required."
],
"package_details": [
"This field is required."
]
}
I've provided the fields.. so why this happening..
You removed those from dict using pop()
The pop() method removes and returns an element from a dictionary having the given key.
Try using get() instead
The get() method returns the value for the specified key if the key is in the dictionary.

Django Nested Serializer - Return Null Field if condition is fullfiled

I have a nested serializes, which content I need to return as Null in case of the parent-serializer field "is_profile_private" (a boolean) is True.
I tried using get_queryset in order to filter the User Profile but no progress was made.
Tried using SerializerMethordField() and get_profile() but Django complained about UserProfileSerializer type of object not being allowed to be serialized.
serializers.py
class UserProfileSerializer(UserSerializer):
height = serializers.SerializerMethodField()
class Meta:
model = UserProfile
fields = (
"bio",
"gender",
"custom_gender",
"non_binary_list",
"birthdate",
"avatar",
"height",
"hometown",
"zodiac_sign",
"language",
)
#staticmethod
def get_height(obj):
return {"value": obj.height, "unit": obj.height_unit}
class SimpleUserSerializer(UserSerializer):
profile = UserProfileSerializer(source="user", required=False)
class Meta:
model = User
fields = (
"id",
"name",
"username",
"is_profile_private",
"date_joined",
"profile",
)
views.py
class UserProfileAPIView(RetrieveModelMixin, UpdateModelMixin, GenericViewSet):
lookup_field = "id"
queryset = User.objects.all()
serializer_class = SimpleUserSerializer
http_method_names = ["get"]
#staticmethod
def get(request, *args, **kwargs):
return User.objects.get(id=str(request.data))
You can use SerializerMethodField:
class SimpleUserSerializer(UserSerializer):
profile = serializers.SerializerMethodField()
class Meta:
model = User
fields = (
"id",
"name",
"username",
"is_profile_private",
"date_joined",
"profile",
)
def get_profile(self, obj):
if obj.is_profile_private:
return None
return UserProfileSerializer(obj.user).data
please note that you should return serializer's data, not serializere itself.

Getting an empty list when using filter - Django REST

I have my API in Django REST Framework:
Here is my models.py:
class myModel(models.Model):
user_email = models.CharField(max_length= 200, null= False)
Here is my views.py:
class GetItemsByEmail(generics.ListAPIView):
def get_queryset(self):
email_items = self.request.query_params.get("user_email")
if(email_items is not None):
itemsReturned = myModel.objects.all().filter(user_email = email_items)
return Response(data= itemsReturned)
Here is my urls.py:
url_patterns = [
path('users/account=<str:id>/shipments', GetItemsByEmail.as_view()),
]
My Question:
I am getting an empty list, getting nothing from making an API call to the above endpoint.
I want to get all the items in the database associated with a particular email?
In your views.py:
from rest_framework import generics
from .models import * # noqa
from .serializers import *
class GetItemsByEmail(generics.ListAPIView):
queryset = MyModel.objects.all() # noqa
serializer_class = MyModelSerializer
def get_queryset(self):
if self.kwargs.get('user_email_pk'):
return self.queryset.filter(id=self.kwargs.get('user_email_pk'))
return self.queryset.all()
In models.py I had to create another model to have the result that you want (get all database by a specific user_email!):
from django.db import models
class MyModel(models.Model):
user_email = models.CharField(max_length=200, null=False)
def __str__(self):
return self.user_email
class ServicesModel(models.Model):
# Just an example to emulate the expected result, do not worry about it!
name = models.CharField('Name', max_length=200)
user_email_service = models.ForeignKey(MyModel, related_name='services', on_delete=models.CASCADE) # Just an example to emulate the expected result, do not worry about it!
def __str__(self):
return self.name
In serializers.py:
from rest_framework import serializers
from .models import MyModel, ServicesModel
class ServiceModelSerializer(serializers.ModelSerializer):
class Meta:
model = ServicesModel
fields = (
'name',
)
class MyModelSerializer(serializers.ModelSerializer):
services = ServiceModelSerializer(many=True, read_only=True)
class Meta:
model = MyModel
fields = (
'id',
'user_email',
'services',
)
In urls.py:
from django.urls import path
from core.views import GetItemsByEmail
urlpatterns = [
path('users/', GetItemsByEmail.as_view(), name='users'), # Ignore!
path('users/account=<str:user_email_pk>/shipments/', GetItemsByEmail.as_view(), name='user_email'),
]
In the test that I made localy I created two 'user_email' and each one have diferent 'services' so you are able to get all the data by the id, images of the result:
You obviously only need to get attention in 'views.py' and 'serializers.py', I just created all this code to get in the expected result!
If you want your query to be case insensitive, you can try the following:
myModel.objects.filter(user_email__iexact=email_items)

'QuerySet' object has no attribute 'simplified'

I am trying to do a fairly simple GET request that leads to a query with DRF:
def get(self, request, character):
char_entry = Dictionary.objects.filter(Q(simplified=character) | Q(traditional=character))
serializer = DictionarySerializer(char_entry)
return Response({"character": serializer.data})
My DictionarySerializer looks like this:
from rest_framework import serializers
from .models import Dictionary
class DictionarySerializer(serializers.ModelSerializer):
class Meta:
model = Dictionary
fields = ["id", "simplified", "pinyin_numbers", "pinyin_marks", "translation", "level", "traditional", ]
And my Dictionary model looks 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()
class Meta:
db_table = 'dictionary'
indexes = [
models.Index(fields=['simplified', ]),
models.Index(fields=['traditional', ]),
]
As far as I can tell, this should serialize all the fields from the Dictionary table, including simplified.
Why can't Django find the attribute? What am I missing?
A QuerySet is a collection of items. It can thus contain zero, one or more items. YOu need to serialize with the many=True parameter:
def get(self, request, character):
char_entry = Dictionary.objects.filter(
Q(simplified=character) | Q(traditional=character)
)
serializer = DictionarySerializer(char_entry, many=True)
return Response({'character': serializer.data})
or if we know there is only one item, we should retrieve that single item:
from django.shortcuts import get_object_or_404
def get(self, request, character):
char_entry = get_object_or_404(
Dictionary,
Q(simplified=character) | Q(traditional=character)
)
serializer = DictionarySerializer(char_entry, many=True)
return Response({'character': serializer.data})

How do I add together fields from a manytomanyfield in django?

I am creating a quote-generator in Django. I want to calculate the total of all items, insert it into a field, and save it.
The models are as follows:
from django.db import models
from django.core.urlresolvers import reverse
class Product(models.Model):
product_name = models.CharField(max_length=50)
product_description = models.CharField(max_length=200)
product_price = models.IntegerField(max_length=4)
def __unicode__(self):
return self.product_name
class Meta:
ordering = ('product_name',)
class Quote(models.Model):
quotee_name = models.CharField("Name", max_length=40)
quotee_email = models.EmailField("Email")
quotee_phone = models.IntegerField("Phone", max_length=10)
quotee_products = models.ManyToManyField(Product, verbose_name="Products")
quotee_total = models.IntegerField("Estimate", max_length=10, null=True, blank=True)
def __unicode__(self):
return self.quotee_email
class Meta:
ordering = ('quotee_email',)
def get_absolute_url(self):
return reverse('quote-detail', kwargs={'pk': self.pk, })
I am not using this through the Admin, so here is the forms.py:
from django import forms
from django.forms import CheckboxSelectMultiple
from InternalDusettenet.apps.quotes.models import Quote
class QuoteForm(forms.ModelForm):
class Meta:
model = Quote
fields = ('quotee_name', 'quotee_email', 'quotee_phone',
'quotee_products')
widgets = {
'quotee_products': CheckboxSelectMultiple(attrs={'size': 10}),
}
And here is the views.py file. I have it set to just save a '1' into the form so that it actually saves. What I want is to replace the '1' with a function that returns the value of 'Product.product_price' for every one selected in 'Quote.quotee_products'. When I create a quote, I select the products, and it gives me the sum of all selected 'product_price' fields related to the selected products:
from django.views.generic.edit import CreateView, UpdateView, DeleteView
from django.views.generic import ListView, DetailView
from django.shortcuts import Http404, get_object_or_404
from InternalDusettenet.apps.quotes.models import Quote
from InternalDusettenet.apps.quotes.forms import QuoteForm
class QuoteCreate(CreateView):
model = Quote
template_name = "quotes/quote_create_edit.html"
fields = ['quotee_name', 'quotee_email', 'quotee_phone',
'quotee_products']
form_class = QuoteForm
def form_valid(self, form):
form.instance.quotee_total = 1
return super(QuoteCreate, self).form_valid(form)
class QuoteList(ListView):
model = Quote
template_name = "quotes/quote_list.html"
class QuoteDetail(DetailView):
model = Quote
template_name = "quotes/quote_detail.html"
class QuoteUpdate(UpdateView):
model = Quote
template_name = "quotes/quote_create_edit.html"
fields = ['quotee_name', 'quotee_email', 'quotee_phone',
'quotee_products', 'quotee_total']
form_class = QuoteForm
class QuoteDelete(DeleteView):
model = Quote
success_url = '/'
template_name = "quotes/quote_delete.html"
I have read the Django docs MANY times but I have no clue how to do this one simple thing.
I am using Django 1.7 and Python 2.7.
No reason to save it in the database, just make it a method or property of the Quote object:
class Quote(models.Model):
...
def quotee_total(self):
return self.quotee_products.aggregate(total=models.Sum('product_price'))['total']
If need be, you can cache the value and fill the cache on the initial query:
class Quote(models.Model):
...
def quotee_total(self):
if not hasattr(self, '_quotee_total'):
self._quotee_total = self.quotee_products.aggregate(total=models.Sum('product_price'))['total']
return self._quotee_total
quotes = Quote.objects.annotate(_quotee_total=models.Sum('quotee_products__product_price'))
You can of course save that value in the database, but there's little reason. If you're worried about performance, that is better handled with caching than with saving the value to the database.
I would not calculate the total in a view. This makes more sense as a method.
class Quote(models.Model):
def calculate_quotee_total(self):
return sum(product.product_price for product in self.quotee_products.all())
def __save__(self):
self.quotee_total = self.calculate_quotee_total()
super(Quote, self).save()
Quote.quotee_total could also be calculated as needed, instead of saving it in the db.
class Quote(models.Model):
#property
def quotee_total(self):
return sum(product.product_price for product in self.quotee_products.all())

Categories