django-tastypie usage without model - python

I need to POST data via AJAX-request to backend python function (that data will be processed with third-party python script) and use the result in the frontend. Currently I am using django-tastypie for API (I am using only ModelResource for my Models). As I understand, I can use Resource to implement this behaviour, but I am a little confused because I don't want to save or store any data, I just want to procced it in the backend. Should I use django-tastypie or maybe it is better to choose another method?

You can use prepend_urls for this
prepend_urls -> A hook for adding your own URLs or matching before the default URLs. Useful for adding custom endpoints or overriding the built-in ones. Tastypie docs link
See below code
class YourModelResource(ModelResource):
class Meta:
queryset = YourModel.objects.all()
resource_name = 'your_model'
def prepend_urls(self):
return [
url(r"^(?P<resource_name>%s)/do_some_work%s$" % (self._meta.resource_name, trailing_slash()), self.wrap_view('do_some_work'), name="api_do_some_work"),
]
def do_some_work(self, request, **kwargs):
self.method_check(request, allowed=['post'])
self.is_authenticated(request)
#Call the script and get the results
results = []
result_list = {
'results': results,
}
return self.create_response(request, result_list)
Here prepend_urls method is overridden to call a nested resource do_some_work. The URI for this call will be like this
/api/v1/your_model/do_some_work/
Above method is suggested if you have to use Tastypie other wise Django views will be best option for this scenario.

Related

How do I specify a custom lookup field for a DRF action on a viewset?

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

Convert GET parameters to POST data on a Request object in Django REST Framework

I am in the process of rewriting the backend of an internal website from PHP to Django (using REST framework).
Both versions (PHP and Django) need to be deployed concurrently for a while, and we have a set of software tools that interact with the legacy website through a simple AJAX API. All requests are done with the GET method.
My approach so far to make requests work on both sites was to make a simple adapter app, routed to 'http://<site-name>/ajax.php' to simulate the call to the Ajax controller. Said app contains one simple function based view which retrieves data from the incoming request to determine which corresponding Django view to call on the incoming request (basically what the Ajax controller does on the PHP version).
It does work, but I encountered a problem. One of my API actions was a simple entry creation in a DB table. So I defined my DRF viewset using some generic mixins:
class MyViewSet(MyGenericViewSet, CreateModelMixin):
# ...
This adds a create action routed to POST requests on the page. Exactly what I need. Except my incoming requests are using GET method... I could write my own create action and make it accept GET requests, but in the long run, our tools will adapt to the Django API and the adapter app will no longer be needed so I would rather have "clean" view sets and models. It makes more sense to use POST for such an action.
In my adapter app view, I naively tried this:
request.method = "POST"
request.POST = request.GET
Before handing the request to the create view. As expected it did not work and I got a CSRF authentication failure message, although my adapter app view has a #csrf_exempt decorator...
I know I might be trying to fit triangle in squares here, but is there a way to make this work without rewriting my own create action ?
You can define a custom create method in your ViewSet, without overriding the original one, by utilizing the #action decorator that can accept GET requests and do the creation:
class MyViewSet(MyGenericViewSet, CreateModelMixin):
...
#action(methods=['get'], detail=False, url_path='create-from-get')
def create_from_get(self, request, *args, **kwargs):
# Do your object creation here.
You will need a Router in your urls to connect the action automatically to your urls (A SimpleRouter will most likely do).
In your urls.py:
router = SimpleRouter()
router.register('something', MyViewSet, base_name='something')
urlpatterns = [
...
path('my_api/', include(router.urls)),
...
]
Now you have an action that can create a model instance from a GET request (you need to add the logic that does that creation though) and you can access it with the following url:
your_domain/my_api/something/create-from-get
When you don't need this endpoint anymore, simply delete this part of the code and the action seizes to exist (or you can keep it for legacy reasons, that is up to you)!
With the advice from all answers pointing to creating another view, this is what I ended up doing. Inside adapter/views.py:
from rest_framework.settings import api_settings
from rest_framework.decorators import api_view, renderer_classes
from rest_framework.response import Response
from rest_framework import status
from mycoreapp.renderers import MyJSONRenderer
from myapp.views import MyViewSet
#api_view(http_method_names=["GET"])
#renderer_classes((MyJSONRenderer,))
def create_entity_from_get(request, *args, **kwargs):
"""This view exists for compatibility with the old API only.
Use 'POST' method directly to create a new entity."""
query_params_copy = request.query_params.copy()
# This is just some adjustments to make the legacy request params work with the serializer
query_params_copy["foo"] = {"name": request.query_params.get("foo", None)}
query_params_copy["bar"] = {"name": request.query_params.get("bar", None)}
serializer = MyViewSet.serializer_class(data=query_params_copy)
serializer.is_valid(raise_exception=True)
serializer.save()
try:
headers = {'Location': str(serializer.data[api_settings.URL_FIELD_NAME])}
except (TypeError, KeyError):
headers = {}
return Response(serializer.data, status=status.HTTP_201_CREATED, headers=headers)
Of course I have obfuscated the names of everything specific to my project. Basically I reproduced almost exactly (except for a few tweaks to my query params) what happens in the create, perform_create and get_success_header methods of the DRF mixin CreateModelMixin in a single function based DRF view. Being just a standalone function it can sit in my adapter app views so that all legacy API code is sitting in one place only, which was my intent with this question.
You can write a method for your viewset (custom_get) which will be called when a GET call is made to your url, and call your create method from there.
class MyViewSet(MyGenericViewSet, CreateModelMixin):
...
def custom_get(self, request, *args, **kwargs):
return self.create(request, *args, **kwargs)
And in your urls.py, for your viewset, you can define that this method needs to be called on a GET call.
#urls.py
urlpatterns = [
...
url(r'^your-url/$', MyViewSet.as_view({'get': 'custom_get'}), name='url-name'),
]
As per REST architectural principles request method GET is only intended to retrieve the information. So, we should not perform a create operation with request method GET. To perform the create operation use request method POST.
Temporary Fix to your question
from rest_framework import generics, status
class CreateAPIView(generics.CreateView):
def create(self, request, *args, **kwargs):
serializer = self.get_serializer(data=request.query_params)
serializer.is_valid(raise_exception=True)
self.perform_create(serializer)
headers = self.get_success_headers(serializer.data)
return Response(
serializer.data,
status=status.HTTP_201_CREATED,
headers=headers)
def get(self, request, *args, **kwargs):
return self.create(request, *args, **kwargs)
Please refer below references for more information.
https://www.w3.org/Protocols/rfc2616/rfc2616-sec5.html
https://learnbatta.com/blog/introduction-to-restful-apis-72/

What would be the robust design pattern for multiple class forms render/process (create, update) on single page in django

I am working on a django project in which I have to design a sales order page for order processing,now as per requirement I have to create multiple information forms on single page with different fields (formset does not helpful in this case) which I achieved by creating specific form class and then render them in template through class view function
def get_context_data(self, **kwargs):
context = super(CreateOrderView, self).get_context_data(**kwargs)
context['ord_info_form'] = OrderInformationForm()
context['billing_form'] = BillingInformationForm(prefix='billing')
context['shipping_form'] = ShipingInformationForm(prefix='shipping')
context['payment_form'] = PaymentInfoForm()
context['summary_form'] = OrderProductsForm()
return context
My template look like this:
Now, I have to save data for all forms which belongs to multiple models like billing, contact and shipping information. So for save information I created another form class for create order and set it in form_class variable in Createview class, and I override the save function in form class in which I manipulate the data for all model forms. For update order (put operation) I did the same I create another form class for update like create one in form class.
As per my understanding I implement this strategy, but this seems to me repetitive design. How can I design it better for multiple forms with different fields. Please let me know what kind of design pattern I can apply for this.
P.S: If require I'll post more code and assets for clear understanding.
Thanks in advance.
I've been working on exactly the same requirement. What we have done is the following:
Create the classbasedview to render the forms and add them to the context just as you did on the top.
I see you've created the template based on the forms you added to the context, but as someone else pointed out you need to use an alternative rather than just django to make the post of each item. In my case I used plain old ajax to perform my post. So it looks something like this:
function firePost() {
var endpoint = '/sales_center/orders/add';
var channel = getChannelForm();
var order = getOrderForm()
var ip = getIPForms();
var collections = getCollections();
var subscriptions = getBookSubs();
var perpetual = getBookPerp();
$.ajax({
type: 'POST',
url: endpoint,
data: {
'order':JSON.stringify(order),
'channel': JSON.stringify(channel),
'ips':JSON.stringify(ip),
'collections': JSON.stringify(collections),
'subscriptions': JSON.stringify(subscriptions),
'perpetual': JSON.stringify(perpetual)
},
success: function(data) {
console.log(data);
},
error: function(data) {
console.log(data);
}
});
}
In the data section I divided the information by name. My order is composed of a channel, multiple collections, multiple ips, multiple subscriptions and multiple perpetual purchases. The endpoint is the same as the one in the page so in the backend in order to process that we do it like this:
#transaction.atomic
def post(self, request):
channel = self._create_channel(json.loads(request.POST.get('channel')))
order = self._create_order(json.loads(request.POST.get('order')), canal)
ips = self._assign_ips(json.loads(request.POST.get('ips')), canal)
return JsonResponse({}, status=200)
The code above is just an example the _create_channel is a function in our classbasedview. Each function performs the posts in our backend for each model. That way it looks clean and easy to read each step of the order.
I added the #transaction.atomic to perform
any rollbacks in case in one critical step of the order we have
inconsistent data and we need to rollback or delete the data saved before in the database.
In each function we then use the forms we sent on the template in the context data, we use them to save or get errors in case something isn't filled out correctly. We do something like this:
def _create_channel(self, data):
form = ChannelForm(data)
if form.is_valid():
new_channel = form.save(commit=False)
new_channel.save()
return new_channel
else:
return JsonResponse(form.errors, status=400)
I hope the example above helps you.
My suggestion would be:
Build a stable(take your time here, you don't want any redundant data) database using django models.
Use Django Rest Framework to build proper serializers and validators against every model.
Route them to urls and perform other linking operations on your views.
Choose a javascript framework, you are comfortable with(Vue.js,Angular2,React which ever you want) and write the frontend separately(this part will have your multiple forms and stuff).
Use axios to post your data in django.
This is my personal suggestion, you can also go for django formsets but that will not be suitable in your example.

Can't use .update() function on Django context in views?

I have a function that gets some base information in my views.py file, and I'm trying to update the context of each page using it by having it return a dictionary. However, using .update() on the context dictionary in the render() function doesn't seem to work.
Here's what I'm doing:
def getBaseInfo():
allPages = list(Page.objects.all())
primaryPages = allPages[:5]
secondaryPages = allPages[5:]
return {'p':primaryPages, 'p2':secondaryPages}
def index(request):
return render(request, 'pages/index.html', {}.update(getBaseInfo()))
However, nothing is sent to my templates. Thanks in advance!
Edit: I'm using Python 2.7.11
Firstly, if you wanted to use a base dictionary and add objects to that you should do so explicitly:
def index(request):
context = getBaseInfo()
context.update({'otherkey': 'othervalue'})
# or
context['otherkey'] = 'othervalue'
return(...)
However, there is no need to do this at all. Django already provides you a way of automatically providing shared context, and that is a context processor.
In fact your getBaseInfo() function is already almost a context processor - it just needs to accept the request parameter - so you just need to add it to the context_processors list in your TEMPLATES setting. Then all your templates will automatically get the values from that function.
You should do something like this:
def index(request):
allPages = list(Page.objects.all())
primaryPages = allPages[:5]
secondaryPages = allPages[5:]
return render(request, 'pages/index.html', {'p':primaryPages, 'p2':secondaryPages})
Other option should be to make getBaseInfo a #property for reusability and DRY purposes, or make the view class based template view and define reusable code as mixin. I prefer the latter, but it's entirely matter of personal choice.

How can I PUT/POST JSON data to a ListSerializer?

I'm reading about customizing multiple update here and I haven't figured out in what case the custom ListSerializer update method is called. I would like to update multiple objects at once, I'm not worried about multiple create or delete at the moment.
From the example in the docs:
# serializers.py
class BookListSerializer(serializers.ListSerializer):
def update(self, instance, validated_data):
# custom update logic
...
class BookSerializer(serializers.Serializer):
...
class Meta:
list_serializer_class = BookListSerializer
And my ViewSet
# api.py
class BookViewSet(ModelViewSet):
queryset = Book.objects.all()
serializer_class = BookSerializer
And my url setup using DefaultRouter
# urls.py
router = routers.DefaultRouter()
router.register(r'Book', BookViewSet)
urlpatterns = patterns('',
url(r'^api/', include(router.urls)),
...
So I have this set up using the DefaultRouter so that /api/Book/ will use the BookSerializer.
Is the general idea that if I POST/PUT/PATCH an array of JSON objects to /api/Book/ then the serializer should switch over to BookListSerializer?
I've tried POST/PUT/PATCH JSON data list to this /api/Book/ that looks like:
[ {id:1,title:thing1}, {id:2, title:thing2} ]
but it seems to still treat the data using BookSerializer instead of BookListSerializer. If I submit via POST I get Invalid data. Expected a dictionary, but got list. and if I submit via PATCH or PUT then I get a Method 'PATCH' not allowed error.
Question:
Do I have to adjust the allowed_methods of the DefaultRouter or the BookViewSet to allow POST/PATCH/PUT of lists? Are the generic views not set up to work with the ListSerializer?
I know I could write my own list deserializer for this, but I'm trying to stay up to date with the new features in DRF 3 and it looks like this should work but I'm just missing some convention or some option.
Django REST framework by default assumes that you are not dealing with bulk data creation, updates, or deletion. This is because 99% of people are not dealing with bulk data creation, and DRF leaves the other 1% to third-party libraries.
In Django REST framework 2.x and 3.x, a third party package exists for this.
Now, you are trying to do bulk creation but you are getting an error back that says
Invalid data. Expected a dictionary, but got list
This is because you are sending in a list of objects to create, instead of just sending in one. You can get around this a few ways, but the easiest is to just override get_serializer on your view to add the many=True flag to the serializer when it is a list.
def get_serializer(self, *args, **kwargs):
if "data" in kwargs:
data = kwargs["data"]
if isinstance(data, list):
kwargs["many"] = True
return super(MyViewSet, self).get_serializer(*args, **kwargs)
This will allow Django REST framework to know to automatically use the ListSerializer when creating objects in bulk. Now, for other operations such as updating and deleting, you are going to need to override the default routes. I'm going to assume that you are using the routes provided by Django REST framework bulk, but you are free to use whatever method names you want.
You are going to need to add methods for bulk PUT and PATCH to the view as well.
from rest_framework.response import Response
def bulk_update(self, request, *args, **kwargs):
partial = kwargs.pop("partial", False)
queryset = self.filter_queryset(self.get_queryset))
serializer = self.get_serializer(instance=queryset, data=request.data, many=True)
serializer.is_valid(raise_exception=True)
self.perform_update(serializer)
return Response(serializer.data)
def partial_bulk_update(self, *args, **kwargs):
kargs["partial"] = True
return super(MyView, self).bulk_update(*args, **kwargs)
This won't work out of the box as Django REST framework doesn't support bulk updates by default. This means you also have to implement your own bulk updates. The current code will handle bulk updates as though you are trying to update the entire list, which is how the old bulk updating package previously worked.
While you didn't ask for bulk deletion, that wouldn't be particularly difficult to do.
def bulk_delete(self, request, *args, **kwargs):
queryset = self.filter_queryset(self.get_queryset())
self.perform_delete(queryset)
return Response(status=204)
This has the same effect of removing all objects, the same as the old bulk plugin.
None of this code was tested. If it doesn't work, consider it as a detailed example.

Categories

Resources