I'm trying to serialize a MPTT tree model with DRF.
My code:
class SiteTreeCalc(serializers.Field):
def to_representation(self, value):
return value.exists() # return True if has children, False otherwise
class SiteTreeSerializer(serializers.ModelSerializer):
children = SiteTreeCalc()
class Meta:
model = SiteTree
fields = ('id', 'site', 'children')
depth = 1
class SiteTreeViewSet(viewsets.ModelViewSet):
#queryset = SiteTree.objects.all()
serializer_class = SiteTreeSerializer
def get_queryset(self):
if 'pk' not in self.kwargs:
# return first-level nodes
return SiteTree.objects.filter(level=0)
else:
# return all children of a given node
return SiteTree.objects.filter(parent__id=int(self.kwargs['pk']))
router = routers.DefaultRouter()
router.register(r'rest/sitetree', SiteTreeViewSet, "SiteTreeRoots")
router.register(r'rest/sitetree/(?P<tree_id>\d+)/$', SiteTreeViewSet, "SiteTreeChildren")
I have two issues with this code:
I have declared parameter "tree_id" in router registration. However, get_queryset says that parameter name is pk
The second filter never works (the one that should return children of given parent). DRF returns "detail": "Not found.". If I test that line in debugger, it naturally returns all children of the given parent.
I seem to be doing something wrong, but the code seems so obvious to me that I just can't see it.
Help - as always - very appreciated.
Turns out, I wanted to forget the convenient functionality of DefaultRouter the first chance I got.
The problem was that I wanted to create a ViewSet just like any other writable ViewSet, but this particular one was intended only for retrieving items. At least, that's what I intended. But DRF couldn't know that, so my problem #2 was a result of DRF actually checking that I'm returning ONE item with EXACTLY the same pk as was given in the URL.
A solution that works goes like this (as suggested in the DRF ViewSets documentation):
class SiteTreeViewSet(viewsets.ReadOnlyModelViewSet):
queryset = SiteTree.objects.filter(level=0)
serializer_class = SiteTreeSerializer
#detail_route()
def children(self, request, pk=None):
data = SiteTree.objects.filter(parent__id=int(pk))
data = self.get_serializer(data, many=True)
return Response(data.data)
This solution returns first-level items in default mode and also accepts /{pk}/children to return children of the given pk node. Naturally, default operations will still return just the pk node when provided with a /{pk}/ URL.
Router registration remains only the default one:
router.register(r'rest/sitetree', SiteTreeViewSet)
As for 1. you need to set lookup_url_kwarg (the named argument in the urls) on the viewset so it maps to the tree_id.
Note that routers do define the dynamic url part themselves.
As for 2. it's most of the time sending JOSN POST data with form content type. Ensure you are sending the right content type in your request's header.
Edit:
Daniel has the point for 1. With your current url patterns, there's no way to distinguish a detailed top node and a list for the child node.
Related
I would like to specify a custom lookup field on the action (different from the viewset default "pk"), i.e.
#action(
methods=["GET"],
detail=True,
url_name="something",
url_path="something",
lookup_field="uuid", # this does not work unfortunately
)
def get_something(self, request, uuid=None):
pass
But the router does not generate the correct urls:
router = DefaultRouter()
router.register(r"test", TestViewSet)
router.urls
yields url:
'^test/(?P<pk>[^/.]+)/something/$'
instead of
'^test/(?P<uuid>[^/.]+)/something/$'
I do not want to change the lookup field for the whole viewset though and have been unsuccessful in finding a way to do this for the action itself after debugging through the router url generation. I did notice that model viewsets have this method:
get_extra_action_url_map(self)
but am unsure how to get it to be called to generate custom urls or if it is even relevant. Any help would be great thanks!
According to their docs you could use a regex lookup field. Their example uses a CBV instead of a request based view.
class MyModelViewSet(mixins.RetrieveModelMixin, viewsets.GenericViewSet):
lookup_field = 'uuid'
lookup_value_regex = '[0-9a-f]{32}'
This could work:
#action(
methods=["GET"],
detail=True,
url_name="something",
url_path="something",
lookup_field = 'uuid'
lookup_value_regex = '[0-9a-f]{32}'
)
def get_something(self, request, uuid=None):
pass
I think it will create much confusion for your API consumers if you have 2 different resource identification on the same resource.
You can name that action query_by_uuid or just allow them to use list_view to filter by uuid if you only want to represent the object tho. (so consumers can use /test/?uuid= to retrieve data)
But if you really want to do it, you can simply override get_object method to filter for your custom action tho:
def get_object(self):
if self.action == 'do_something':
return get_object_or_404(self.get_queryset(), uuid=self.kwargs['pk'])
return super().get_object()
Here is a bit hacky solution for generate uuid in router with detail=False.
#action(detail=False, url_path=r'(?P<uuid>[^/.]+)/do_something')
def do_something(self, request, uuid=None):
pass
I have a queryset that I'm trying to serialize with my CasePartySerializer which inherits the Django REST Framework ModelSerializer class. It has a nested serializer called CaseRuleDocketSerializer.
Here is how I instantiate it.
def get_queryset(self):
self.queryset = CaseParty.objects.filter(user=self.request.user)
def get(self):
serial = CasePartySerializer(
list(
self.queryset[
self.offset:self.offset + self.limit
]
),
many=True,
context={
'tracked': self.tracked.value,
'request': self.request
}
)
Simple Enough, but what if I want to conditionally pass over and reject objects so they are not included in the finalized serial.data.
I'm sure there is probably some exception I can raise that would pass over this database object, but I'm unsure what that would be. I looked through the documentation without any luck; it's something that surprised me considering the quality of the REST Framework documentation. I'm probably missing something simple.
Here is my CasePartySerializer so you can see my conditionals. In this example you can see they are based on results from the nested serializer CaseRuleDocketSerializer output which is not available from the get_queryset method. If one of the ruledocket items is 'red' it does not need to be included in the serializer result. I know I can filter in the get_queryset method also, but it just seems it would be easier to do in the serializer itself.
class CaseRuleDocketSerializer(serializers.ModelSerializer):
table_row_color = serializers.SerializerMethodField()
class Meta:
model = CaseRuleDocket
fields = [
'unique_id',
'created_date',
'updated_date',
'date_time',
'entry',
'party'
]
def get_is_user_only_created(self, obj):
if obj.is_user_created and not obj.is_court_created:
return 'green'
elif obj.is_court_created and not obj.is_user_created:
return 'blue'
else:
return 'red'
class CasePartySerializer(serializers.ModelSerializer):
docket_of_party = CaseRuleDocketSerializer(many=True, read_only=True)
table_row_color = serializers.SerializerMethodField()
class Meta:
model = CaseParty
fields = [
'is_tracked',
'created_date',
'updated_date',
'serve_status',
'serve_status_date',
'defendant',
'plaintiff',
# CONTINUE WITH THE REST OF THE FIELDS....
]
def get_table_row_color(self, obj):
errors = [
x.table_row_color for x in self.obj.docket_party
]
if 'blue' in errors:
return 'blue'
elif 'green' in errors:
return 'green'
else:
# HERE IS WHERE I WANT TO EXCEPT AND REMOVE
# THIS CaseParty OBJECT INSTANCE
I have been using stackoverflow for many years and I have always found the answers I have needed when looking. For some reason I could not find a way to frame the question correctly. Also, I am familiar with the documentation and have read it thoroughly. If it is not suggested to filter inside of the queryset due to efficiency, readability, or some other missight on my part, please include the reason in your answer.
Let me know if you need clarification.
Thanks!
Based on your example, where you are filtering all CaseParty instances that have no related CaseTrack models, your best bet would be to update the view's get_queryset method.
Basically, rather than using queryset = CaseParty.objects.all(), you could write your own get_queryset(self) method, which will filter out the unwanted models.
To give a more concrete example:
class MyViewSet(ModelViewSet):
def get_queryset(self):
# We fetch all party instances
party_instances = CaseParty.objects.all()
# We fetch all case_tracks linked to them
case_tracks = CaseTrack.objects.get(
user=self.request.user,
case__in=party_instances
)
# We extract the party ids from case tracks
party_ids_from_casetracks = {item.case.id for item in case_tracks}
# We only keep those parties in the queryset
party_instances_with_tracks = [item for item in party_instances if item.id in party_ids_from_casetracks ]
return party_instances_with_tracks
You'll find more example in the official documentation
If filterting at the viewset level is problematic because different endpoints/views must have different filters, then simply filter then from within the view action.
As someone else mentionned in the comments, the serializer is not here to filter the data:
The view filters the data (queryset, get_queryset, filter_queryset, detail view, etc)
The serializer receives the data, validates it, create/update/destroy instances, return representation and internal values
I have a model which creates Memo objects. I would like to use a custom Model Manager's posted method to return the total number of Memo objects - then use this number within a template. I am trying to keep as much of my code as possible within my Models and Model Managers and less within my Views as I read that this was a best practice in 'Two Scoops of Django'.
In the shell I can get the number of memos as such:
>>> from memos.models import Memo
>>> Memo.objects.all()
<QuerySet [<Memo: Test Memo 2>, <Memo: Test Memo 1>]>
>>> Memo.objects.all().count()
2
This is what my Model and Model Manager look like:
class MemoManager(models.Manager):
use_for_related_fields = True
def posted(self):
return self.count()
class Memo(models.Model):
title = models.CharField(max_length=100)
content = models.TextField()
date_time = models.DateTimeField(default=timezone.now)
author = models.ForeignKey(User, on_delete=models.CASCADE)
objects = MemoManager()
def __str__(self):
return self.title
def get_absolute_url(self):
return reverse('memos-detail', kwargs={'pk': self.pk})
I know this is clearly the wrong way to do it but I have confused myself here. So how do I use my Model Manager to get the count of objects and use it in a template like: {{ objects.all.count }}?
P.S. I see other posts that show how to do this within the view but as stated I am trying not to use the view. Is using the view required? I also understand my posted method is written incorrectly.
I'm sorry but you have misinterpreted what was written in TSD. The Lean View Fat Model is meant to keep code which pertains to 'business logic' out of the views, and certain model specific things. A request should be handled by a view. So when you want to load a template, you must first have a GET request to your app.
A view function should be written such that Validation of POST data or the Creation of a new object in DB or Querying/Filtering for GET requests should be handled in the corresponding serializer/model/model manager.
What should be happening while you want to load your template.
Have a url for the template that you have created and a view function mapped for it
In the view function you should render said template and pass the necessary data inside the context.
To keep in line with the Lean View Fat Model style, if you want to get a Queryset of of Memo's but only those which have their is_deleted fields set to False, you can overwrite the model manager get_queryset() method for Memo model.
If you want to create a new Memo with a POST request, you can handle
the creation using a ModelForm!
Hope this clears things up!
EDIT:
How to pass a context to a template, in your case the memo count.
def random_memo_view(request):
context = {'memo_count': Memo.posted()}
return render(request, 'template.html', context=context)
RE-EDIT
I just checked that you were using DetailView. In this case follow this from the django docs.
Class Based Views: Adding Extra Context
I've been learning Django over the past couple of weeks, and there is one thing that really seems to confuse me. Which model's attributes does Django use to define the <pk> that is used in urls.py?
For example, If I have:
urlpatterns = [
url(r'^(?P<pk>\d+)/$', ProductDetailView.as_view(), name="product-detail"),
]
I had assumed previously that the pk would be derived from the model instance that is being used in the given view, in this case, ProductDetailView.as_view(). However, I'm starting to question that logic as you can pass multiple models into a view, of course.
Part 2
Also, what if I wanted to use the pk of one model instance, while only using a different model instance in the view?
For example, what if I had two models, Products & Stores which both hold a many-to-many relationship (eg. a product could be in multiple stores, and a store can hold many products). Then I wanted to have a url where I have a StoreListView listing all the stores that hold a given product, so my url would be something like:
url(r'^(?P<pk>\d+)/$', StoreListView.as_view(), name="store-list")
Where the pk is of the Product instance but the view is of the Store instance
To finalize the question, again, how does Django define pk?
Django doesn't define anything. You define it. ProductDetailView must have a model attribute; it is that attribute that defines what model to use.
Unfortunately, part 2 of your question doesn't really make sense; a view is not an instance of a model.
Have a look at the various methods and attributes of the detail view here, particularly the get_object method:
http://ccbv.co.uk/projects/Django/1.9/django.views.generic.detail/DetailView
def get_object(self, queryset=None):
"""
Returns the object the view is displaying.
By default this requires `self.queryset` and a `pk` or `slug` argument
in the URLconf, but subclasses can override this to return any object.
"""
# Use a custom queryset if provided; this is required for subclasses
# like DateDetailView
if queryset is None:
queryset = self.get_queryset()
# Next, try looking up by primary key.
pk = self.kwargs.get(self.pk_url_kwarg)
slug = self.kwargs.get(self.slug_url_kwarg)
if pk is not None:
queryset = queryset.filter(pk=pk)
# Next, try looking up by slug.
if slug is not None and (pk is None or self.query_pk_and_slug):
slug_field = self.get_slug_field()
queryset = queryset.filter(**{slug_field: slug})
# If none of those are defined, it's an error.
if pk is None and slug is None:
raise AttributeError("Generic detail view %s must be called with "
"either an object pk or a slug."
% self.__class__.__name__)
try:
# Get the single item from the filtered queryset
obj = queryset.get()
except queryset.model.DoesNotExist:
raise Http404(_("No %(verbose_name)s found matching the query") %
{'verbose_name': queryset.model._meta.verbose_name})
return obj
It's Django, so its fairly customizable, so it doesn't need to be called 'PK'. You could override that by using the pk_url_kwarg. By default the id field is the pk, unless you specify it in your model definition.
Django's DetailView uses the SingleObjectMixin mixin which has a method called get_object which will look for a pk_url_kwarg
pk = self.kwargs.get(self.pk_url_kwarg)
By default this is set to 'pk'
I have a django-rest-framework REST API with hierarchical resources. I want to be able to create subobjects by POSTing to /v1/objects/<pk>/subobjects/ and have it automatically set the foreign key on the new subobject to the pk kwarg from the URL without having to put it in the payload. Currently, the serializer is causing a 400 error, because it expects the object foreign key to be in the payload, but it shouldn't be considered optional either. The URL of the subobjects is /v1/subobjects/<pk>/ (since the key of the parent isn't necessary to identify it), so it is still required if I want to PUT an existing resource.
Should I just make it so that you POST to /v1/subobjects/ with the parent in the payload to add subobjects, or is there a clean way to pass the pk kwarg from the URL to the serializer? I'm using HyperlinkedModelSerializer and ModelViewSet as my respective base classes. Is there some recommended way of doing this? So far the only idea I had was to completely re-implement the ViewSets and make a custom Serializer class whose get_default_fields() comes from a dictionary that is passed in from the ViewSet, populated by its kwargs. This seems quite involved for something that I would have thought is completely run-of-the-mill, so I can't help but think I'm missing something. Every REST API I've ever seen that has writable endpoints has this kind of URL-based argument inference, so the fact that django-rest-framework doesn't seem to be able to do it at all seems strange.
Make the parent object serializer field read_only. It's not optional but it's not coming from the request data either. Instead you pull the pk/slug from the URL in pre_save()...
# Assuming list and detail URLs like:
# /v1/objects/<parent_pk>/subobjects/
# /v1/objects/<parent_pk>/subobjects/<pk>/
def pre_save(self, obj):
parent = models.MainObject.objects.get(pk=self.kwargs['parent_pk'])
obj.parent = parent
Here's what I've done to solve it, although it would be nice if there was a more general way to do it, since it's such a common URL pattern. First I created a mixin for my ViewSets that redefined the create method:
class CreatePartialModelMixin(object):
def initial_instance(self, request):
return None
def create(self, request, *args, **kwargs):
instance = self.initial_instance(request)
serializer = self.get_serializer(
instance=instance, data=request.DATA, files=request.FILES,
partial=True)
if serializer.is_valid():
self.pre_save(serializer.object)
self.object = serializer.save(force_insert=True)
self.post_save(self.object, created=True)
headers = self.get_success_headers(serializer.data)
return Response(
serializer.data, status=status.HTTP_201_CREATED,
headers=headers)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
Mostly it is copied and pasted from CreateModelMixin, but it defines an initial_instance method that we can override in subclasses to provide a starting point for the serializer, which is set up to do a partial deserialization. Then I can do, for example,
class SubObjectViewSet(CreatePartialModelMixin, viewsets.ModelViewSet):
# ....
def initial_instance(self, request):
instance = models.SubObject(owner=request.user)
if 'pk' in self.kwargs:
parent = models.MainObject.objects.get(pk=self.kwargs['pk'])
instance.parent = parent
return instance
(I realize I don't actually need to do a .get on the pk to associate it on the model, but in my case I'm exposing the slug rather than the primary key in the public API)
If you're using ModelSerializer (which is implemented by HyperlinkedModelSerializer) it's as easy as implementing the restore_object() method:
class MySerializer(serializers.ModelSerializer):
def restore_object(self, attrs, instance=None):
if instance is None:
# If `instance` is `None`, it means we're creating
# a new object, so we set the `parent_id` field.
attrs['parent_id'] = self.context['view'].kwargs['parent_pk']
return super(MySerializer, self).restore_object(attrs, instance)
# ...
restore_object() is used to deserialize a dictionary of attributes into an object instance. ModelSerializer implements this method and creates/updates the instance for the model you specified in the Meta class. If the given instance is None it means the object still has to be created, so you just add the parent_id attribute on the attrs argument and call super().
So this way you don't have to specify a read-only field, or have a custom view/serializer.
More information:
http://www.django-rest-framework.org/api-guide/serializers#declaring-serializers
Maybe a bit late, but i guess this drf nested routers library could be helpful for that operation.