I'm a little bit stuck with the following situation. I want to build a REST API for a shopping cart app using the Django Rest Framework, however, due to legacy requirements I need to work with nested URLs.
In general, I have two resources AppUsers and Carts. Both of the resources are available at the default /appUsers/ and /carts/ endpoints. I then tried using nested routers to get the cart detail view for a specific user to be addressable as /appUsers/app_user_pk/cart/ instead of /carts/pk/ since every AppUser can only have one cart anyway.
Here's my setup:
models.py
class AppUser(models.Model):
_id = models.AutoField(primary_key=True)
class Meta:
default_related_name = 'app_users'
class Cart(models.Model):
app_user = models.OneToOneField(
'AppUser',
on_delete=models.CASCADE,
related_query_name='cart',
)
class Meta:
default_related_name = 'carts'
def __str__(self):
return "{user} cart".format(user=self.app_user._id)
serializers.py
class AppUserSerializer(serializers.HyperlinkedModelSerializer):
cart = serializers.HyperlinkedIdentityField(view_name='cart-detail')
class Meta:
model = AppUser
fields = '__all__'
class CartSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = Cart
fields = '__all__'
views.py
class AppUserViewSet(viewsets.ModelViewSet):
"""
Model viewset for AppUser model
"""
queryset = AppUser.objects.all()
serializer_class = AppUserSerializer
class CartViewSet(viewsets.ModelViewSet):
"""
List all carts, or create new / edit existing product.
"""
queryset = Cart.objects.all()
serializer_class = CartSerializer
def get_queryset(self):
if 'app_user_pk' in self.kwargs:
return Cart.objects.filter(app_user=self.kwargs['app_user_pk'])
return Cart.objects.all()
urls.py
router = DefaultRouter()
router.register(r'appUsers', views.AppUserViewSet)
router.register(r'carts', views.CartViewSet)
urlpatterns = [
url(r'^', include(router.urls)),
url(r'^appUsers/(?P<app_user_pk>[0-9]+)/cart/$', views.CartViewSet.as_view({'get': 'retrieve'}), name='cart-detail'),
]
However, I can't get it to work, whenever I try something new I run into a different issue. So I was wondering what the best way is to achieve such task?
Essentially I just want to have cart-detail view for the one shopping cart the an AppUser can have under /appUsers/app_user_pk/cart/
SOLUTION:
I used the accepted answer to deal with the issue above. Additionally I had another ModelViewSet located at /appUsers/app_user_pk/cart/products, which I then registered using a NestedDefaultRouter from drf-nested-routers at cart/products like this:
cart_products_router = routers.NestedDefaultRouter(router, r'appUsers', lookup='app_user')
cart_products_router.register(r'cart/products', views.CartProductViewSet, base_name='cartproduct')
You can create a custom method to AppUserViewSet:
class AppUserViewSet(viewsets.ModelViewSet):
"""
Model viewset for AppUser model
"""
queryset = AppUser.objects.all()
serializer_class = AppUserSerializer
#action(detail=True)
def cart(self, request, pk):
obj = Cart.objects.get(app_user_id=pk)
serializer = CartSerializer(obj)
return Response(serializer.data)
Related
I'm using Django 2.2 and Django REST Framework.
I have the following model structure
class MyModel(models.Model):
name = models.ChartField(max_length=200)
class Tag(models.Model):
name = models.ChartField(max_length=50, unique=True)
class MyModelRelation(models.Model):
obj = models.ForeignKey(MyModel, related_name='relation')
user = models.ForeignKey(User)
tags = models.ManyToManyField(Tag)
def tag_list(self):
return self.tags.all().values_list('name', flat=True).distinct()
I want to get the tags list with the MyModel instance and for that, the serializer is
class MyModelSerializer(serializers.ModelSerializer):
tags_list = serializers.SerializerMethodField(read_only=True)
def get_tags_list(self, obj):
return obj.relation.tag_list()
class Meta:
fields = [
'name',
'tags_list'
]
and the view is
class ObjListView(ListAPIView):
serializer_class = MyModelSerializer
def get_queryset(self):
return super().get_queryset().select_related('relation').prefetch_related('relation__tags')
But to get 58 records, it is running almost 109 queries.
The my_app_mymodel`, `my_app_mymodelrelation_tags is repeated multiple times
This is how I suggest you solve the problem. Instead of extracting the name in the DB level, you can do it in the serializer level. It will make things way easier and faster. First, remove the tag_list method from the model class. First add the annotation to your views:
from django.db.models import F
def get_queryset(self):
return super().get_queryset().annotate(tags_list=F('relation__tags')).select_related('relation')
Then in your serializers
class MyModelSerializer(serializers.ModelSerializer):
tags_list = serializers.SlugRelatedField(many=True, slug_field='name', read_only=True)
...
Hello Django Programmers,
I have an issue which I don't understand.
Here is my model class:
class Profile(models.Model):
name = models.CharField(max_length=50)
employeeView = models.BooleanField(default=True)
owner = models.OneToOneField(User, related_name="profile", on_delete=models.CASCADE, null=True)
This class extends my User Model. Please notice that I'm using here OneToOneField in relation to User Model.
This model is serialized with that Serializer:
class ProfileSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = Profile
fields = (
'url',
'pk',
'name',
'employeeView')
And finally I have view which I use to process GET (list) and POST requests to the database:
below view works fine (for GET request), but it gives me all Profiles related with all users
class ProfileList(generics.ListCreateAPIView):
permission_classes = [
permissions.IsAuthenticated
]
serializer_class = ProfileSerializer
name = 'profile-list'
queryset = Profile.objects.all()
def perform_create(self, serializer):
serializer.save(owner=self.request.user)
Because I would like to create view which could give me only profile for My user, I modifiet above view like so:
class ProfileList(generics.ListCreateAPIView):
permission_classes = [
permissions.IsAuthenticated
]
serializer_class = ProfileSerializer
name = 'profile-list'
##queryset = Profile.objects.all()
def get_queryset(self):
return self.request.user.profile.all()
def perform_create(self, serializer):
serializer.save(owner=self.request.user)
But when I send the GET request I get this error:
{code}
'Profile' object has no attribute 'all'
{code}
So my question here is more general. Above view will work when I will use in my Profile model ForeignKey field instead of OneToOneField. In such case when in my view I will request for all objects which belongs to my user like so:
def get_queryset(self):
return self.request.user.profile.all()
I will get necessary data. But how to ask for objects when we have OneToOneField? It seems that the procedure is different, but I can not find it anywhere.
all() implies that there are many related objects, so this is applicable for reverse one-to-many (ForeignKey) and many-to-many (ManyToManyField) calls.
In case of one-to-one (OneToOneField), as the name suggests, one object can be related to only one of the object of the related model, hence there's no point of all-like methods here.
Now, the get_queryset method is supposed to return a queryset of objects, not a single one. So you can not use self.request.user.profile as that refers to only one instance.
You need to return a queryset with the only instance of Profile that is related to the requesting User:
def get_queryset(self):
return Profile.objects.filter(owner=self.request.user)
The documentation https://docs.djangoproject.com/en/2.2/topics/db/examples/one_to_one/ says that you would access the user profile by using user.profile. This makes sense, since it’s a OneToOneField and so we shouldn’t expect a QuerySet, just a single object.
Using Django REST framework I created an url which maps to a page with a JSON file containing all the objects in my database.
I want to do the same but instead of showing all the objects I want only the objects that match a specific category (category is an attribute in my model).
I have urls that show a JSON files with a single object in it (using the pk attribute) but when I try to do the same thing with category instead of pk I get a MultipleObjectsReturned error.
I'm just sperimenting with the REST framework, I tried using different views and class based views solving nothing.
Any hint or suggestion is really appreciated thanks.
# models.py
class Hardware(models.Model):
name = models.CharField(max_length=25)
category = models.CharField(choices=CATEGORY_CHOICES, max_length=2)
def get_api_url(self):
return api_reverse("category-api-postings:post-rud", kwargs={'category': self.category})
#views.py
class HardwareListView(generics.ListCreateAPIView):
pass
lookup_field = 'pk'
serializer_class = HardwareSerializer
def get_queryset(self):
query = self.request.GET.get("q")
qs = Hardware.objects.all()
if query is not None:
qs = qs.filter(Q(title__icontains=query) | Q(content__icontains=query)).distinct()
return qs
class HardwareRudView(generics.RetrieveUpdateDestroyAPIView):
pass
lookup_field = 'category'
serializer_class = HardwareSerializer
def get_queryset(self):
return Hardware.objects.all()
#urls.py
app_name = 'category-api-postings'
urlpatterns = [
path('', exercise_view),
path('list-api/', HardwareListView.as_view(), name='all'),
path('list-api/<str:category>/', HardwareRudView.as_view(), name='post-rud')
#serializer.py
class HardwareSerializer(serializers.ModelSerializer):
url = serializers.SerializerMethodField(read_only=True)
class Meta:
model = Hardware
fields = [
'url',
'pk',
'name',
'category'
]
read_only_fields = ['user']
def get_url(self, obj):
return obj.get_api_url()
As I understand, you want url /list-api/HD/ to return all Hardware objects from given category. For that HardwareRudView must inherit ListAPIView, not RetrieveUpdateDestroyAPIView. For example, like this:
class HardwareRudView(generics.ListAPIView):
serializer_class = HardwareSerializer
def get_queryset(self):
category = self.kwargs['category']
return Hardware.objects.filter(category=category)
See related docs: https://www.django-rest-framework.org/api-guide/filtering/#filtering-against-the-url
I have a django project that I am working on. There are two models in this project. There is a user and account model. I am integreating the django rest framework viewsets. I will include them below. I am now integrating the Django Rest Framework in the project. I am trying to digure out how to do two things.
2 models:
Default django user
Account Model:
class Account(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE)
_id = models.CharField(max_length=45)
name = models.CharField(max_length=140)
balance = models.DecimalField(max_digits=100, decimal_places=2)
currency = models.CharField(max_length=12)
bank_name = models.CharField(max_length=120)
routing = models.CharField(max_length=8)
_class = models.CharField(max_length=22)
type = models.CharField(max_length=22)
active = models.BooleanField(default=True)
main = models.BooleanField(default=False)
synapse = models.BooleanField(default=False)
create_at = models.DateTimeField(auto_now_add=True)
def __str__(self):
return self.user.username + ' - ' + self.name + ': ' + str(self.balance)
1 => I want to be able to type in the endpoint /api/users/accounts/omarjandlai and grab the single or many acounts with the omarjandali user foreignkey
2 => I want to be able to type into the following
api/users/accounts/ and return all of the accounts that are in the database
I tried 4 - 5 different ways to get this to work and I couldnt get it to work.
Here are my serializers and views
serializers:
class AccountSerializer(serializers.ModelSerializer):
class Meta:
model = Account
fields = ('user', '_id', 'name', 'balance', 'currency', 'bank_name',
'routing', '_class', 'type', 'active', 'main', 'synapse')
Views:
class AccountViewSet(viewsets.ModelViewSet):
queryset = Account.objects.all();
serializer_class = AccountSerializer
filter_backends = (filters.DjangoFilterBackend,)
filter_fields = ('user',)
what is happening is that when I do the
api/users/account i see all the accounts
api/users/account/omarjandali I get detail not found
urls:
router = DefaultRouter()
router.register(r'users', UserViewSet, basename='user')
router.register(r'accounts', AccountViewSet, basename='account')
urlpatterns = router.urls
I can see that you are using DefaultRouter´s default functionality. That way you only can access records detail by using theirs primary key. Since you didn't specify any field as primary key explicitly (primary=True), Django sets field id, which is PositiveIntegerField as the primary key of the model. So, if you want to access the detail of a record you must use api/users/account/112/.
You can access records by name using filters: api/users/account/?search='omarjandali', but you need to add SearchFilter.
from rest_framework.filters import SearchFilter
class AccountViewSet(viewsets.ModelViewSet):
queryset = Account.objects.all();
serializer_class = AccountSerializer
filter_backends = (filters.DjangoFilterBackend, SearchFilter)
filter_fields = ('user',)
search_fields = ('name',)
If you want to stick to the url pattern api/users/account/omarjandali/, you have to add it to urls.py and use another viewset in order to let the original one to function accordingly to DefaultRouter definition:
urls.py:
router = DefaultRouter()
router.register(r'users', UserViewSet, basename='user')
router.register(r'accounts', AccountViewSet, basename='account')
urlpatterns = [
path('', include(router.urls)),
path('api/users/account/<str:username>/', views.GetRecordsByNameViewSet.as_view({'get': 'list'}),
]
views.py
from rest_framework.response import Response
class GetRecordsByNameViewSet(viewsets.ViewSet):
def list(self, request, username):
accounts = Account.objects.filter(user__username=username)
accounts_to_return = AccountSerializer(accounts, many=True).data
return Response(accounts_to_return)
You can try overriding
get_queryset (method)
With this no need to provide a username/email in URL. You would do localhost8000/accounts, it would only show accounts for the respective users.
class AccountViewSet(viewsets.ModelViewSet):
queryset = Account.objects.all();
serializer_class = AccountSerializer
filter_backends = (filters.DjangoFilterBackend,)
filter_fields = ('user',)
def get_queryset(self):
queryset = super().get_queryset()
queryset = queryset.filter(user=self.request.user)
return queryset
Note: This will work if authentication is set in the project. In the case of token authentication, you would need to provide a token in the authorization header.
I want to know if the following is possible and if someone could explain how. I'm using Django REST Framework
I have a model, in that model I have a class called Product. Product has method called is_product_safe_for_user. It requires the user object and the self (product).
model.py
class Product(models.Model):
title = models.CharField(max_length=60, help_text="Title of the product.")
for_age = models.CharField(max_length=2,)
def is_product_safe_for_user(self, user):
if self.for_age > user.age
return "OK"
(ignore the syntax above, its just to give you an idea)
What I want to do is run the method for to all of the queryset objects, something like below, but I don't know how...
class ProductListWithAge(generics.ListAPIView):
permission_classes = (permissions.IsAuthenticated,)
model = Product
serializer_class = ProductSerializer
def get_queryset(self):
Product.is_product_safe_for_user(self,user)
# then somehow apply this to my queryset
return Product.objects.filter()
there will also be times when I want to run the methoud on just one object.
Or should it go into the Serializer? if so how?...
class ProductSerializer(serializers.ModelSerializer):
safe = serializers.Field(Product='is_product_safe_for_user(self,user)')
class Meta:
model = Product
fields = ('id', 'title', 'active', 'safe')
You could write a custom manager for your model. Something like this:
class OnlySafeObjects(models.Manager):
def filter_by_user(self, user):
return super(OnlySafeObjects, self).get_query_set().filter(for_age__gte=user.age)
class Product(models.Model):
# your normal stuff
onlysafeobjects = OnlySafeObjects()
Then you would use it like this:
safe_products = Product.onlysafeobjects.filter_by_user(request.user)