Limit choices to foreignkey in django rest framework - django

How to limit images of request.user to be linked with node. I wish I could do something like:
photo = models.ForeignKey(
Image,
limit_choices_to={'owner': username},
)
but request.user rather than username and I don't want to use local threads.
models.py
class Node(models.Model):
owner = models.ForeignKey(User)
content = models.TextField()
photo = models.ForeignKey(Image)
class Image(models.Model):
owner = models.ForeignKey(User)
file = models.ImageField(upload_to=get_upload_file_name)
serializers.py
class ImageSerializer(serializers.ModelSerializer):
owner = serializers.Field('owner.username')
class Meta:
model = Image
fields = ('file', 'owner')
class NodeSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = Node
fields = ('content', 'photo', 'owner')

I would deal with this by overriding get_serializer_class to dynamically return a serializer class at runtime, setting the choices option on the field there:
def get_serializer_class(self, ...):
user = self.request.user
owner_choices = ... # However you want to restrict the choices
class ImageSerializer(serializers.ModelSerializer):
owner = serializers.Field('owner.username', choices=owner_choices)
class Meta:
model = Image
fields = ('file', 'owner')
return ImageSerializer

You can create a custom foreign key field and define get_queryset() method there to filter related objects to only those of your user. The current user can be retrieved from the request in the context:
class UserPhotoForeignKey(serializers.PrimaryKeyRelatedField):
def get_queryset(self):
return Image.objects.filter(owner=self.context['request'].user)
class NodeSerializer(serializers.HyperlinkedModelSerializer):
photo = UserPhotoForeignKey()
class Meta:
model = Node
fields = ('content', 'photo', 'owner')
This example is using Django REST Framework version 3.

class CustomForeignKey(serializers.PrimaryKeyRelatedField):
def get_queryset(self):
return Table.objects.filter(is_active=True)
class Serializer(serializers.ModelSerializer):
(...)
table= CustomForeignKey()
class Meta:
(...)
even more easy is :
class Serializer(serializers.ModelSerializer):
(...)
table = serializers.PrimaryKeyRelatedField(queryset=Table.objects.filter(is_active=True))
class Meta:
(...)

Because I am sure this logic will be used across an entire Django application why not make it more generic?
class YourPrimaryKeyRelatedField(serializers.PrimaryKeyRelatedField):
def __init__(self, **kwargs):
self.model = kwargs.pop('model')
assert hasattr(self.model, 'owner')
super().__init__(**kwargs)
def get_queryset(self):
return self.model.objects.filter(owner=self.context['request'].user)
serializers.py
class SomeModelSerializersWithABunchOfOwners(serializers.ModelSerializer):
photo = YourPrimaryKeyRelatedField(model=Photo)
categories = YourPrimaryKeyRelatedField(model=Category,
many=True)
# ...

from rest_framework import serializers
class CustomForeignKey(serializers.PrimaryKeyRelatedField):
def get_queryset(self):
return Table.objects.filter(user=self.context['request'].user)
# or: ...objects.filter(user=serializers.CurrentUserDefault()(self))
class Serializer(serializers.ModelSerializer):
table = CustomForeignKey()

Related

Filtering foreign key data by created_by field using Class-Based-View

This is my models.py
class InvoiceLine(AbstractSaleLine):
invoice = models.ForeignKey('books.Invoice',
related_name="lines")
name = models.ForeignKey('books.Item')
tax_rate = models.ForeignKey('books.TaxRate')
class Meta:
pass
class Item(models.Model):
item = models.CharField(max_length=255)
created_by = models.ForeignKey(User, on_delete=models.CASCADE, related_name="Item", default=1)
views.py
class InvoiceCreateView(generic.CreateView):
template_name = "books/invoice_create_or_update.html"
model = Invoice
form_class = InvoiceForm
formset_class = InvoiceLineFormSet
success_url = reverse_lazy("books:invoice-list")
forms.py
class InvoiceLineForm(RestrictLineFormToOrganizationMixin,
ModelForm):
class Meta:
model = InvoiceLine
fields = (
"name",
"tax_rate",
"item_id"
)
How do i filter Item the foreign key by field created_by using CBV? I am using CreateView.
You can override get_queryset, to get current user use self.request.user. To filter by related model's field use __ notation:
class InvoiceCreateView(generic.CreateView):
template_name = "books/invoice_create_or_update.html"
model = InvoiceLine
form_class = InvoiceForm
formset_class = InvoiceLineFormSet
success_url = reverse_lazy("books:invoice-list")
def get_queryset(self):
return InvoiceLine.objects.filter(name__created_by=self.request.user)
You can override the queryset for the ModelChoiceField when the form is initialised:
class InvoiceLineForm(RestrictLineFormToOrganizationMixin, ModelForm):
def __init__(self, *args, **kwargs):
user = kwargs.pop('user')
self.fields['item'].queryset = Item.objects.filter(created_by=user)
class Meta:
model = InvoiceLine
fields = (
"name",
"tax_rate",
"item" # Note this should be `item` not `item_id`
)
You then need to pass the user to the form when you initialise the formset - something like:
formset = formset_class(form_kwargs={'user': self.request.user})

django rest framework : nested model get not working. 'str' object has no attribute 'values'

I have a customer model in Bcustomer app that extends the django User model, So I will save the basic details such as name in User table and the remaining data (city, etc) in customer table.
Saving is working perfectly. But now it is showing the following error when I call the GET method.
AttributeError at /api/v1/customer 'str' object has no attribute 'values'
Request Method: GET
bcustomer/models.py
class BCustomer(models.Model):
customer = models.OneToOneField(settings.AUTH_USER_MODEL, on_delete=models.CASCADE, primary_key=True, blank=True )
address = models.CharField(max_length=50)
city = models.CharField(max_length=256)
state = models.CharField(max_length=50)
user = models.ForeignKey(settings.AUTH_USER_MODEL, db_index=True, on_delete=models.CASCADE, related_name='customer_creator')
# more fields to go
def __str__(self):
# return str(self.name) (This should print first and last name in User model)
class Meta:
app_label = 'bcustomer'
bcusomer/serializers.py
class CustomerDetailsSerializer(serializers.ModelSerializer):
class Meta:
model = BCustomer
fields = ('city', 'phone')
class CustomerSerializer(serializers.ModelSerializer):
customer_details = CustomerDetailsSerializer()
class Meta:
model = get_user_model()
fields = ('id','first_name', 'email', 'customer_details')
def create(self, validated_data):
request = self.context.get('request')
customer_details_data = validated_data.pop('customer_details')
customer_user = get_user_model().objects.create(**validated_data)
BCustomer.objects.create(customer=customer_user, user=request.user, **customer_details_data)
customer_user.customer_details = customer_details_data
return customer_user
class CustomerListSerializer(serializers.ModelSerializer):
model = get_user_model()
fields = '__all__'
class Meta:
model = get_user_model()
fields = '__all__'
bcustomer/views.py
class CustomerViewSet(viewsets.ModelViewSet):
customer_photo_thumb = BCustomer.get_thumbnail_url
permission_classes = [permissions.IsAuthenticated, TokenHasReadWriteScope]
queryset = BCustomer.objects.all()
serializer_class = CustomerSerializer
def get_queryset(self):
queryset = BCustomer.objects.all()
return queryset
def get_serializer_class(self):
if self.action == 'list' or self.action == 'retrieve':
return CustomerListSerializer
return CustomerSerializer
bcustomer/urls.py
router.register(r'customer', views.CustomerViewSet, 'customers')
Data post parameter format
{
"first_name":"Myname",
"email":"testemail#gmail.com",
"customer_details": {
"city":"citys",
"phone":"04722874567",
}
}
You should remove model and fields from CustomListSerializer
class CustomerListSerializer(serializers.ModelSerializer):
class Meta:
model = get_user_model()
fields = '__all__'
customer_details = CustomerDetailsSerializer()
You need to set the source argument to point to the user model's customer. Most probably:
customer_details = CustomerDetailsSerializer(source='customer')
(or maybe source='bcustomer', not sure if it reversed the field name or class name).
On a side not, you should not need the ListSerializer at all. The list method will call the serializer with the many=True argument on CustomerSerializer which will create the ListSerializer appropriately.

django rest framework limit_choices_to attribute ignored?

I'm using the DjangoRest Framework with 2 models DeviceType and Channel. Now Channel has a ForeignKeyField pointing to a DeviceType. No problem so far.
But now I don't want all DeviceTypes to be selectable when adding or editing a Channel but only the DeviceTypes that have their usesChannels field set to True.
So I used the limit_Choices_to attribute but somehow that doesn't seem to work. No matter what I do, I alway get a list with all DeviceTypes including the ones with usesChannels set to False
This is my code:
models.py
class DeviceType(models.Model):
name = models.CharField(max_length=30)
usesChannels = models.BooleanField()
def __str__(self):
return '%s' % (self.name)
class Channel(models.Model):
type = models.ForeignKey(DeviceType, limit_choices_to={'usesChannels': True})
name = models.CharField(max_length=30)
channelNr = models.IntegerField()
serializers.py
class DeviceTypeSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = DeviceType
fields = ('url', 'name', 'usesChannels')
class ChannelSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = Channel
flieds = ('url', 'type', 'name', 'channelNr')
I used the ForeignKey.limit_choices_to example from This link
Edit: I use the DRF browsable API to add, edit and remove data.
Answer: After struggling with this for a few days I found a working solution:
models.py
class DeviceType(models.Model):
name = models.CharField(max_length=30)
usesChannels = models.BooleanField()
def __str__(self):
return '%s' % (self.name)
class Channel(models.Model):
type = models.ForeignKey(DeviceType)
name = models.CharField(max_length=30)
channelNr = models.IntegerField()
serializers.py
class DeviceTypeSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = DeviceType
fields = ('url', 'name', 'usesChannels')
class ChannelSerializer(serializers.HyperlinkedModelSerializer):
type = serializers.PrimaryKeyRelatedField(queryset=DeviceType.objects.filter(usesChannels=True))
class Meta:
model = Channel
flieds = ('url', 'type', 'name', 'channelNr')
Edit: For completeness I'll add the views too
Views.py:
class DeviceTypeViewSet(viewsets.ModelViewSet):
queryset = DeviceType.objects.all()
serializer_class = DeviceTypeSerializer
class ChannelViewSet(viewsets.ModelViewSet):
queryset = Channel.objects.all()
serializer_class = ChannelSerializer
Ran into this problem today. I believe it does indeed completely ignore the attribute; at least I couldn't find any reference to it in DRF's code.
So I "solved" the problem by adding Serializer.__init__ constructor and within it the following function / code:
class MySerializer(Serializers.Serializer):
def __init__(self, *args, **kwargs):
...
def limit_choices_to(field_name):
fld = self.fields[field_name]
fld.queryset = fld.queryset.filter(**Flight._meta.get_field(field_name).get_limit_choices_to())
limit_choices_to('my_field')

Django REST Framework (DRF): Set current user id as field value

I have model NewsModel and 2 serializers for him:
models.py
class NewsModel(models.Model):
title = models.CharField('Заголовок', max_length=255, help_text='Максимальная длина - 255 символов')
announce = models.TextField('Анонс', help_text='Краткий анонс новости')
author = models.ForeignKey(settings.AUTH_USER_MODEL, help_text='Автор новости', related_name='news')
full_text = models.TextField('Полный текст новости', help_text='Полный текст новости')
pub_date = models.DateTimeField('Дата публикации', auto_now_add=True, default=timezone.now, help_text='Дата публикации')
def comments_count(self):
return NewsComment.objects.filter(news=self.id).count()
def get_author_full_name(self):
return self.author.get_full_name()
class Meta:
db_table = 'news'
ordering = ('-pub_date',)
serilizers.py:
from rest_framework import serializers
from .models import NewsModel
from extuser.serializers import UserMiniSerializer
class NewsReadSerializer(serializers.ModelSerializer):
author = UserMiniSerializer()
class Meta:
model = NewsModel
fields = ('id', 'title', 'announce', 'comments_count', 'reviews', 'author_name')
def get_author_full_name(self, obj):
return obj.get_author_full_name()
class NewsWriteSerializer(serializers.ModelSerializer):
def validate_author(self, value):
value = self.request.user.id
return value
class Meta:
model = NewsModel
I select serializers in the api.py:
class NewsList(ListCreateAPIView):
queryset = NewsModel.objects.order_by('-pub_date')
def get_serializer_class(self, *args, **kwargs):
if self.request.method == 'GET':
return NewsReadSerializer
return NewsWriteSerializer
class Meta:
model = NewsModel
But when I will create NewsModel item, I see Error 400: Bad request [{'author': 'This field is required'}]
How I can set current user id as NewsItem.author value on creating new item?
I don't think you're using the serializer properly. A better practice to set request related data is to override perform_create in your view:
def perform_create(self, serializer):
serializer.save(author=self.request.user)
def perform_update(self, serializer):
serializer.save(author=self.request.user)
and then set your author serializer to read-only:
author = UserMiniSerializer(read_only=True)
this way you can simply use one single NewsSerializer for both read and write actions.
In new DRF you can write
owner = serializers.HiddenField(
default=serializers.CurrentUserDefault()
)
See http://www.django-rest-framework.org/api-guide/validators/#currentuserdefault
In DRF version prior 3 field must be declader with allow_null=True and default=None. DRF don't run checking fields without this params. Result code:
class NewsReadSerializer(serializers.ModelSerializer):
"""
Serializer only for reading.
author field serialized with other custom serializer
"""
author = UserMiniSerializer()
class Meta:
model = NewsModel
fields = ('id', 'title', 'announce', 'comments_count', 'reviews', 'author', 'pub_date',)
class NewsWriteSerializer(serializers.ModelSerializer):
"""
Serializer for creating and updating records.
author here is the instance of PrimaryKeyRelatedField, linked to all users
"""
author = serializers.PrimaryKeyRelatedField(
queryset=User.objects.all(), # Or User.objects.filter(active=True)
required=False,
allow_null=True,
default=None
)
# Get the current user from request context
def validate_author(self, value):
return self.context['request'].user
class Meta:
model = NewsModel
fields = ('title', 'announce', 'full_text', 'author',)
I would try something like this:
your models.py
class NewsModel(models.Model):
title = models.CharField(
'Заголовок', max_length=255,
help_text='Максимальная длина - 255 символов')
announce = models.TextField('Анонс',
help_text='Краткий анонс новости')
author = models.ForeignKey(
settings.AUTH_USER_MODEL,
help_text='Автор новости', related_name='news')
full_text = models.TextField(
'Полный текст новости',
help_text='Полный текст новости')
pub_date = models.DateTimeField(
'Дата публикации', auto_now_add=True,
default=timezone.now, help_text='Дата публикации')
def comments_count(self):
return NewsComment.objects.filter(news=self.id).count()
def get_author_full_name(self):
return self.author.get_full_name()
class Meta:
db_table = 'news'
ordering = ('-pub_date',)
serializers.py
(ref.: http://www.django-rest-framework.org/api-guide/validators/#currentuserdefault)
from <yourapp>.models import NewsModel
from rest_framework import serializers
class NewsModelSerializer(serializers.ModelSerializer):
author = serializers.HiddenField(default=serializers.CurrentUserDefault())
class Meta:
model = NewsModel
Also you should set settings.py to something like this:
REST_FRAMEWORK = {
'DEFAULT_PERMISSION_CLASSES': (
'rest_framework.permissions.IsAuthenticated',)
}

django REST framework - limited queryset for nested ModelSerializer?

I have a ModelSerializer, but by default it serializes all the objects in my model. I would like to limit this queryset to only the most recent 500 (as opposed to all 50 million). How do I do this?
What I have currently is the following:
class MyModelSerializer(serializers.ModelSerializer):
class Meta:
model = MyModel
The reason I don't think I can just specify the queryset in my viewset is that this is in fact the nested portion of another serializer.
models.py
class Container(models.Model):
size = models.CharField(max_length=20)
shape = models.CharField(max_length=20)
class Item(models.Model):
container = models.ForeignKey(Container, related_name='items')
name = models.CharField(max_length=20)
color = models.CharField(max_length=20)
views.py
class ContainerViewSet(viewsets.ModelViewSet):
queryset = Container.objects.all() # only a handful of containers
serializer_class = ContainerSerializer
serializers.py
class ItemSerializer(serializers.ModelSerializer):
class Meta:
model = Item
fields = ('name', 'color')
class ContainerSerializer(serializers.ModelSerializer):
items = ItemSerializer(many=True) # millions of items per container
class Meta:
model = Container
fields = ('size', 'shape', 'items')
In your View Set you may specify the queryset like follows:
from rest_framework import serializers, viewsets
class MyModelSerializer(serializers.ModelSerializer):
class Meta:
model = MyModel
class MyModelViewSet(viewsets.ModelViewSet):
queryset = MyModel.objects.all()[:500]
serializer_class = MyModelSerializer
I think what you are looking for is the SerializerMethodField.
So your code would look as follows:
class ContainerSerializer(serializers.ModelSerializer):
items = SerializerMethodField('get_items')
class Meta:
model = Container
fields = ('size', 'shape', 'items')
def get_items(self, container):
items = Item.objects.filter(container=container)[:500] # Whatever your query may be
serializer = ItemSerializer(instance=items, many=True)
return serializer.data
The one catch is that the SerializerMethodField is read only.
You may use source parameter
class Container(models.Model):
...
def get_items(self):
return self.items[:500]
and in serializer
items = ItemSerializer(many=True, source='get_items', )