Django TastyPie Geo Distance Lookups - django

I'm using TastyPie for Geo-distance lookups. That is a bit difficult, because oficially its not supported by TastyPie. On Github (https://gist.github.com/1067176) I found the following code-sample:
def apply_sorting(self, objects, options=None):
if options and "longitude" in options and "latitude" in options:
return objects.distance(Point(float(options['latitude']), float(options['longitude']))).order_by('distance')
return super(UserLocationResource, self).apply_sorting(objects, options)
It works well, but now I would like to have the distance as a field result in TastyPie. Do you have any idea how to do that? Just including "distance" in the fields attribute doesn't work.
Thanks in advance for your help!

The fields defined in the meta attributes aren't enough to have the additional values returned.
They need to be defined as additional fields in the resource:
distance = fields.CharField(attribute="distance", default=0, readonly=True)
This value can be filled by defining dehydrate_distance method inside the resource
def dehydrate_distance(self, bundle):
# your code here
or by adding some additional elements to queryset in resources meta like so:
queryset = YourModel.objects.extra(select={'distance': 'SELECT foo FROM bar'})
Tastypie itself appends a field called resource_uri that isn't actually present in the queryset, looking at the source code of tastypie's resources might be helpful for you too.

Related

Parse GraphQL query to find fields to be able to prefetch_related?

When using DjangoListObjectType from graphene_django_extras, I can define a custom qs property on a SearchFilter.
The qs function has the object as its only argument, and through that I can get the request, and in turn the query string which includes the queried fields.
Before hacking together my own parser to get these fields, am I going about this the wrong way? Or is there something else out there?
The idea is to have quite a rigid approach, as we have 7 types of paginated list types with fields that result in a few unnecessary database hits, so we want to prefetch a few fields.
Graphene has a dataloader approach which kind of looks right, but more complicated than just prefetching them at the qs stage.
Re the dataloader approach, I tried something like this
class UserLoader(DataLoader):
def batch_load_fn(self, keys):
users = {user.id: user for user in User.objects.filter(id__in=keys)}
return Promise.resolve([users.get(user_id) for user_id in keys])
user_loader = UserLoader()
class BookType(DjangoObjectType):
...
author = graphene.Field(UserType)
def resolve_author(self, *args, **kwargs):
return user_loader.load(self.author.id)
Which kind of obviously feels completely wrong, but is what they do in the docs.
Using a DataLoader is the correct approach but do self.author_id instead of self.author.id.
By calling self.author.id you're fetching the author for each book (one SQL query per author) then getting the id attribute from each author.
When you add the author ForeignKey to your Book model, behind the scenes, Django adds a column to the books table—author_id. You can pass this value to your DataLoader and have it fetch all the corresponding Authors in a single SQL query using a SELECT IN.
https://docs.djangoproject.com/en/3.2/ref/models/fields/#database-representation
You'll notice that the Graphene Docs are doing the same thing with best_friend_id in their example.

"Cannot resolve keyword 'created' into field" on new DRF list view

I made a new list view in my Django REST Framework app:
class ColumnView(ListCreateAPIView):
queryset = Column.objects.all()
serializer_class = ColumnSerializer
permission_classes = [IsAuthenticatedOrReadOnly]
def perform_create(self, serializer):
serializer.save(user=self.request.user)
When I try to access it, I get the following error:
FieldError at /my/new/endpoint
Cannot resolve keyword 'created' into field. Choices are: _order, fields, from, my, model
There's no created field anywhere in sight - not in the ColumnSerializer, not in the Column Django model, nowhere. The stacktrace is really opaque, too - my app doesn't appear anywhere in it. What's going on?
Did you enable cursor pagination in your settings?
REST_FRAMEWORK = {
...
'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.CursorPagination',
...
}
Be sure to read the docs on CursorPagination carefully:
Details and limitations
Proper use of cursor based pagination requires a little attention to detail. You'll need to think about what ordering you want the scheme to be applied against. The default is to order by "-created". This assumes that there must be a 'created' timestamp field on the model instances, and will present a "timeline" style paginated view, with the most recently added items first.
In other words, you can't have all three of these conditions:
Using cursor pagination
...without specifying an ordering
...on a model without a created field

Django rest framework hyperlinkrelatedfield for one table using primary key

I have a table called 'users' and 'location'. Users table has a foreign key that relates to location table. I have a users serializer to get the JSON. What would I do to get the hyperlinks for the users table using its primary key?
In django rest framework documentation, I couldn't find a solution. I tried using hyperlinkrelatedfield. But still I couldn't achieve this. Can someone help me in finding the solution?
Using rest-framework HyperlinkedRelatedField does not work because it was never built to expose the URL of the object being requested. Mainly because since the client already has the url of the user, why send it back again? Nevertheless you can achieve this by doing something like this.
class UserSerializer(serializers.ModelSerializer):
user_url = serializers.SerializerMethodField()
class Meta:
model = User
def get_label_location(self, obj):
return HyperlinkedRelatedField(view_name='user-detail',
read_only=True) \
.get_url(obj, view_name='label-detail',
request=self.context['request'], format=None)
Take note on a few things,
view-name param to the HyperlinkedRelatedField should be based on your url configuration
read-only has to be true since otherwise you'll have to specify the queryset. But since we have the object needed to generate the url we can ignore that.
I've set format param to None but you might want to set it based on your settings.
You can read up about SerializerMethodField here.

Django Tastypie: Allow Random as an Order By Option

Per this question, I know how to randomly order a queryset in the Meta class of a tastypie Resource, but is there a way to make it an available order_by option instead of making it the default? It looks like anything defined in the ordering Meta setting must also be listed in the fields setting and ? obviously isn't a field. Without that I simply get,
{"error": "No matching '?' field for ordering on."}
You can override the apply_sorting method (documentation) on your Resource, maybe something like this (untested):
class YourResource(ModelResource):
def apply_sorting(self, obj_list, options=None):
if options and '?' in options.get('order_by', ''):
return obj_list.order_by('?')
return super(YourResource, self).apply_sorting(obj_list, options)
You might need to copy code from the ModelResource implementation for getting the correct order_by value if this doesn't work as-is.

How do you save the order of a django inline formset?

In Django, I'm using an inlineformset with can_order = True, on a model that has order_with_respect_to set. I've set up drag and drop on the front end, which results in Django's autogenerated ORDER form fields (which I've hidden) changing to reflect the new order. I've verified I'm POSTing the new order correctly to the server, but Django seems to ignore it, and saves the models in the original order. The automatically-created _order fields in the database never change.
How can I get Django to save the models using order specified in the formset? Do I need to do anything special other than calling save() on the formset?
Evan Borgstrom proposed solution does not work together with can_delete=True.
Here is my solution that also works with can_delete=True:
for instance in formset.save(commit=False):
# ... do something with m2m relationships ...
for form in formset.ordered_forms:
form.instance.order = form.cleaned_data['ORDER']
form.instance.save()
I had the same problem and after digging through the Django source figured that it's something you need to do on your own.
My original implementation looked something like this:
services = service_formset.save(commit=False)
for service in services:
# ... do something with m2m relationships ...
service.save()
However, as you've found this doesn't set the ORDER. So instead my implementation now looks like:
for form in service_formset:
service = form.save(commit=False)
# ... do something with m2m relationships ...
service.order_order = form.cleaned_data['ORDER']
service.save()
In my example above 'order_order' is the field I'm using to track order on my models.
Also remember that you need to specify the 'ordering' attribute of your model's Meta class to ensure that when you generate the formset again the elements are in the correct order.
class Service(models.Model):
# ...
order_order = models.IntegerField(default=0)
class Meta:
ordering = ['order_order',]