Django REST framework, get object from URL - django-rest-framework

I'm wondering if there is a clean way to retrieve an object from its URL with django rest framework. Surely there should be, as it seems to be what's happening when using HyperlinkedRelatedField.
For instance, I have this URL /api/comment/26 as a string. From my view, how can I get the comment instance with pk=26?
Of course I could redo the work and work on the string but it must be a better way?
Thanks a lot.
EDIT:
This is how I solved it at the end:
resolve('/api/comment/26/').func.cls.model will return my model Comment.
resolve('/api/category/1/').kwargs['pk'] will return the pk.
Which gives you:
from django.core.urlresolvers import resolve
resolved_func, unused_args, resolved_kwargs = resolve('/api/category/1/')
resolved_func.cls.model.objects.get(pk=resolved_kwargs['pk'])

I suspect your best bet would be to manually keep a map of models to url patterns — not too dissimilar from a URLConf.
If that doesn't rock your boat you could pass the path into resolve.
This will give you a ResolverMatch upon which you'll find the func that was returned by the as_view call when you set up your URLs.
The __name__ attribute of your view function will be that of your original view class. Do something like globals()[class_name] to get the class itself.
From there access the model attribute.
I hope that helps. As I say, you might just want to map models to URLs yourself.

With class-based views, something like the following seems needed:
resolve(url).func.cls.serializer_class.Meta.model.objects.get(
**resolve(url).kwargs)

Solution above did not work for me, the following did work though:
from django.core.urlresolvers import resolve
resolved_func, unused_args, resolved_kwargs = resolve('/api/category/1/')
resolved_func.cls().get_queryset().get(id=resolved_kwargs['pk'])
additionally this solution uses the built in queryset of your view, which might have annotations or important filters.
Using HyperlinkedModelSerializer I actually needed to do this with a full url. For this to work, you need to extract the path first, resulting in:
import urllib.parse
from django.core.urlresolvers import resolve
def obj_from_url(url):
path = urllib.parse.urlparse(url).path
resolved_func, unused_args, resolved_kwargs = resolve(path)
return resolved_func.cls().get_queryset().get(id=resolved_kwargs['pk'])

Related

Enforcing use of format suffix patterns in DRF with ViewSets

I'm using Django Rest Framework to make a read-only API from an existing Django website. I'd like to require API URLs to include a format suffix (either .json, or .api for the browsable API) but I can't see how to do this when using ViewSets and Routers.
In my main site urls.py I have:
from django.urls import include
# ...
urlpatterns += [
path("api/v1/", include("api.urls", namespace="v1")),
]
And api/urls.py contains:
from django.urls import include, path
from rest_framework.routers import DefaultRouter
from . import views
app_name = "api"
router = DefaultRouter(trailing_slash=False)
router.register(r"people", views.PersonViewSet)
urlpatterns = [
path("", include(router.urls)),
]
Two issues:
I can get a browsable API page by going to either /api/v1/people or /api/v1/people.api – I'd like to enforce the use of the latter.
The api-root view is visible at /api/v1/ or /api/v1/.api. That last looks ugly! But I can't see how to make it /api/v1.api, given I use a trailing slash on all URLs on the site, apart from the API.
Because I feel like I'm fighting DRF on this, I also feel I'm missing what the common best practice is.
At Andrew Backer's suggestion I tried using my own version of DefaultRouter and have, I think, solved point (1).
If I copy DefaultRouter, give it a new name, and make a single change, it enforces the use of format extensions. The only change is in the get_urls() method, where I change this:
if self.include_format_suffixes:
urls = format_suffix_patterns(urls)
To this:
if self.include_format_suffixes:
urls = format_suffix_patterns(urls, suffix_required=True, allowed=["api", "json"])
The suffix_required argument is the trick. It's a shame there's no way to pass an argument into DefaultRouter in order to toggle this on or off.
I tried subclassing DefaultRouter and only overriding the get_urls() method (rather than entirely copying and replacing the class) but that generated ImproperlyConfigured exceptions, "…is not a valid regular expression: redefinition of group name 'format' as group 2; was group 1 at position 42". I think that results in format_suffix_patterns() getting called twice, which is similar to an Issue here.

Django Rest Framework get last element from database

I think it's simple but I cannot find the solution.
I want to get last element from the database. And create some endpoint for my rest.
I have some working solution:
views.py
class LastElementViewSet(viewsets.ModelViewSet):
serializer_class = ElementSerializer
# here I got the model Element type. So I can use it to get last_unix:
last_unix = Element.objects.last().unix
# and use it (last_unix) to get last element (Queryset type):
queryset = Element.objects.filter(unix=last_unix)
But I'm pretty sure that Django provides some better solution :D
Do you have any idea how to do something like this?
Best regards!

Should I use singletableview?

I'm learning about Django tables. I first wrote a basic example, here my view:
def people1(request):
table = PersonTable(Person.objects.filter(id=2))
RequestConfig(request).configure(table)
return render(request, 'people.html', {'table': table})
This way I've been able to easily display a table with a filter condition "filter(id=2))".
After that I found SingleTableView which is supposed to be an easier way to display database tables, as an example I wrote this view, which worked fine:
from django_tables2 import SingleTableView
class PersonList(SingleTableView):
template_name = 'ta07/comun.html'
model = Person
table_class = PersonTable
Questions are: how should I do to apply filters like in the first example? And is SingleTableView better than the basic way?
I'd say for now, you should only use it for the very basic use case. As soon as you need customizations from that, use your own.
Since filtering is a very common use case, I might consider adding that to the features of SingleTableView at some point. If you need it before that, feel free to open a pull request.

How to populate one dropdown from the other in django dynamically using AJAX/DAJAX/DAJAXICE/Simple Javascript?

I have searched enough of the examples but couldn't get the satisfactory result. Please explain with all the necessary code. I am very poor at AJAX. I tried to use DAJAXICE in my code and got little success but didn't work with passing parameters.
I am using Django 1.6 Dajaxice 0.7 Dajax 0.9.
Any way you feel the easiest is okay but please explain with all the code.
TIA.
If all you need is a simple Django view to fetch some data with AJAX, take a look at django-braces AjaxResponseMixin. Below is a code sample which returns list of objects ids and their names:
from django.views.generic import View
from braces import views
class SomeView(views.JSONResponseMixin, views.AjaxResponseMixin, View):
def get_ajax(self, request, *args, **kwargs):
my_objects = MyObject.objects.all()
json_dict = {
'ids': [x.id for x in my_objects],
'names': [x.name for x in my_objects]
}
return self.render_json_response(json_dict)
Then use jQuery ajax to make the query from your template and populate your fields.
If you're not familiar with Class Based Views, your url for this view could be:
url('^objects/(?P<some_id>[0-9]+)/$', SomeView.as_view())
then in get_ajax you can access self.kwargs['some_id'] to filter objects.

How do I create an empty Django formset using modelformset_factory?

I'm creating a formset, but it seems to populate it with all of the existing data in the table for that object. I can't figure out how to start with a blank formset; the only way seems to be to delete all of the data from the table, but clearly this isn't an option.
I will post code if necessary (but there's lots of it, so knowing what is relevant is tricky).
give a parameter queryset=Model.objects.none() when making the object.
Following Arihant's answer, I did something like this, which works:
class TagCreateFormSet(BaseModelFormSet):
def __init__(self, data=None, files=None, auto_id='id_%s', prefix=None,
queryset=None, **kwargs):
queryset = Tag.objects.none()
super(TagCreateFormSet, self).__init__(data, files,
auto_id, prefix, queryset, **kwargs)
It seems that it isn't possible to change the behaviour of model formset. So as a solution, I changed the data structure so that the data I want to edit is grouped by another type, and then instead I have used inlineformset_factory.

Resources