How to access a Django method from Angular 7 - django

Im pretty new to full stack development so this might actually be an easy question:
I want to be able to access a simple method in the models.py file of my Django backend:
def testMethodFromModelPY(request, path=''):
data = {'returnedData': 'whatever'}
return data
I add the path to the method in urls.py:
from django.urls import path
from DjangoBackEnd import models
urlpatterns = [
# some other paths here that frontend is able to access #
path(r'api/v1/', models.testMethodFromModelPY, name='testMethodFromModelPY'),
]
Now, within Angular frontend I create a component called config in the app folder and add to config.service.ts
export interface Test {
returnedData: string;
}
#Injectable()
export class ConfigService {
constructor(private http: HttpClient) { }
testMethodInConfigServiceTS() {
return this.http.get('/api/v1/testMethodFromModelPY/');
}
}
I include this Service in app.module.ts and call the testMethodInConfigServiceTS function in config.component.ts:
testMethodInConfigComponentTS() {
this.configService.testMethodInConfigServiceTS()
.subscribe(
(data: Test) => this.test = { ...data }, // success path
error => this.error = error // error path
);
}
Within config.component.html I try to access this method with:
<button (click)="testMethodInConfigComponentTS()">teststuffhere</button>
But when I clic a button I get the errormessage:
GET http://127.0.0.1:8000/api/v1/testMethodFromModelPY/ 404 (Not Found)
thanks a lot!

So what you're looking for is angular -> url -> view -> model but you seem to be missing the view in your logic.
Let's break it down.
Add a view to your django application. Django views are functions and/or classes that allow you to handle http requests. They're called by your urls. You can read more about them here. For example your view may look something like this:
from django.http import HttpResponse
from DjangoBackEnd import models
def test_view(request):
output = models.testMethodFromModelPY()
return HttpResponse(output)
Map your views in your urls file.
from django.urls import path
from .views import test_view
urlpatterns = [
# some other paths here that frontend is able to access #
path(r'api/v1/', test_view, name='testMethodFromModelPY'),
]
This will now allow you to hit example-domain.com/api/v1/ which should provide you with the output of your test function in your models. Notice that I haven't added the name of the url aka name='testMethodFromModelPY' as this is used as reference in your templates Jinja code and Djangos internals. You can read more about urls here.
Saying that you'll have to amend your endpoint from:
testMethodInConfigServiceTS() {
return this.http.get('/api/v1/testMethodFromModelPY/');
}
to
testMethodInConfigServiceTS() {
return this.http.get('/api/v1/');
}
Take a look at djangorestframework which is a great toolbox for api driven applications with a django backend.
Good luck!

Make an API and send some requests with angular
Django Rest Framework is easy and fast to set up
https://www.django-rest-framework.org/

Related

Django Swagger Rest API, tryit now function when api is on different server to ui

I am trying to document our Django Rest API using Django Swagger Rest API where our API is on different server to UI
Trying to change the TryIt now URL for the curl request as the API is on a different server to the UI
I have tried setting url in get_swagger_view, but this appends ui url before this url
I have tried changing base_path in SWAGGER settings but nothing works
Assuming that you tried absolute url and it didn't work, another way to do that is customize the generator instead of using get_swagger_view function. You can change whatever you need, for instance:
from rest_framework import response, schemas
from rest_framework.decorators import api_view, renderer_classes
from rest_framework_swagger.renderers import OpenAPIRenderer, SwaggerUIRenderer
class CustomRenderer(OpenAPIRenderer):
def get_customizations(self):
data = super().get_customizations()
data['host'] = 'yourdomain.com/path'
return data
#api_view()
#renderer_classes([SwaggerUIRenderer, CustomRenderer])
def swagger_view(request):
generator = schemas.SchemaGenerator(title='Your title')
return response.Response(generator.get_schema(request=request))
Don't forget to configure this function in your urls.py:
urlpatterns = [
url(r'docs^$', swagger_view)
]

Uploading file using axios

Hi I'm trying to upload a file to server and I'm having difficulty sending the file. I'm using Django at the backend and I've already set up a put endpoint allowing users to send files and I confirmed it works as intended using postman. However, I'm having trouble uploading on the frontend. So this is how my code looks on the frontend end.
selectFileManually.addEventListener('change', function(event){
event.stopPropagation();
event.preventDefault();
axios.put('http://127.0.0.1:8000/api/v1/fileupload/', {
file: this.files[0]
}).then(resp => console.log(resp.data)).catch(err => console.log(err.response.data))
}
}
})
Here selectFileManually is an input[type='file']. However, when I send this request the server comes back with the following error: "Missing filename. Request should include a Content-Disposition header with a filename parameter and when I look at the payload it's complete empty: `{file: {}}' even though you can clearly see I provided a file to send. This is how my code looks at the backend
# views.py
from django.shortcuts import render
from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework.parsers import FileUploadParser, MultiPartParser
import os
# Create your views here.
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
class FileUploadView(APIView):
parser_classes = (MultiPartParser, FileUploadParser, )
def put(self, request, format=None):
print(request.data)
with open(os.path.join(BASE_DIR, 'api/media', request.data['file'].name), 'wb') as f:
for chunk in request.data['file'].chunks():
f.write(chunk)
return Response(status=204)
#urls.py
from django.urls import path
from . import views
urlpatterns = [
path('fileupload/', views.FileUploadView.as_view()),
]
Could somebody please help me. I know that this.files[0] is not empty since the console logged the files and it showed me that it was indeed the right content
According to the docs, you need to specify a filename parameter for the view to handle the request. This can be done in two ways:
1. The view can be called using the filename URL keyword argument, then that argument is set as the filename.To achieve this you need to change your url pattern to
urlpatterns = [
path('fileupload/(?P<filename>[^/]+)$', views.FileUploadView.as_view()),
]
2. If the view is called without the filename URL keyword argument, then the client must set it in the Content-Disposition HTTP header. It is then handled by FileUploadParser in the views.
For eg Content-Disposition: attachment; filename=image.jpg.
Either way, you need to access filename while making a request.
In your case, since your url pattern does not contain a filename argument you need to set headers to include Content-Disposition.
Assuming this.files[0] is a fileObject, you can get filename by simply doing this.files[0].name. Now setting headers on the axios request, so your front end code should look like this.
selectFileManually.addEventListener('change', function(event){
event.stopPropagation();
event.preventDefault();
axios.put('http://127.0.0.1:8000/api/v1/fileupload/', {
file: this.files[0]
},{headers:{
'Content-Disposition': 'attachment; filename=this.files[0].name'
}
},
).then(resp => console.log(resp.data)).catch(err => console.log(err.response.data))
}
}
})
Hope this helps !

Extending custom router to default router across apps in Django Rest Framework

I have come across a problem regarding having the API apps seperate, while still being able to use the browsable API for navigation.
I have previously used a seperate routers.py file in my main application containing the following extension of the DefaultRouter.
class DefaultRouter(routers.DefaultRouter):
def extend(self, router):
self.registry.extend(router.registry)
Followed by adding the other application routers like this:
from . routers import DefaultRouter
from app1.urls import router as app1_router
# Default Router
mainAppRouter = DefaultRouter()
mainAppRouter.extend(app1_router)
where the app1_router is a new SimpleRouter object.
Now the problem occurs when I want to modify the SimpleRouter and create my own App1Router, such as this
class App1Router(SimpleRouter):
routes = [
Route(
url = r'^{prefix}{trailing_slash}$',
mapping = {
'get': 'retrieve',
'post': 'create',
'patch': 'partial_update',
},
name = '{basename}-user',
initkwargs = {}
),
]
This will not handle my extension correctly. As an example, GET and PATCH are not recognized as allowed methods whenever I extend the router, but when I dont extend, but only use the custom router, everything works fine.
My question is therefor, how can I handle extending custom routers across seperate applications, but still maintain a good browsable API?
The router registry is just a standard python list, so you can call YourRouter.registry.extend() directly on registy withouth the need to subclass the DefaultRouter, check the source of DRF here .
Since registry is a list, when you extend registry you add another python list on top of python list, which implies calling YourRouter.registry.extend(app_router.registry).
What worked for me, was importing routers from another apps (SimpleRouters) and adding them on top of default router registry.
#aplication_root/urls.py
from django.urls import include, path
from rest_framework.routers import DefaultRouter
from app_3.urls import router as app_3_router
router = DefaultRouter()
router.registry.extend(app_3_router.registry)
urlpatterns = [
path('api/', include(router.urls)),
]
If your are trying to add versioning to your app by adding the prefix, I suggest to take a look at versioning schema available in DRF - maybe it could fit your needs
DRF versioning
for example with URLPathVersioning enabled in your settings
REST_FRAMEWORK = {
'DEFAULT_VERSIONING_CLASS': 'rest_framework.versioning.URLPathVersioning'
}
urlparttern list from snipped above would look like that
urlpatterns = [
path('api/<version>/', include(router.urls)),
]
Have you tried leaving the default routes intact for SimpleRouter?
class App1Router(SimpleRouter):
def __init__(self):
super(SimpleRouter, self).__init__()
self.routes.insert(0, Route(
url = r'^{prefix}{trailing_slash}$',
mapping = {
'get': 'retrieve',
'post': 'create',
'patch': 'partial_update',
},
name = '{basename}-user',
initkwargs = {}
))
I don't see this anywhere in your code:
where the app1_router is a new SimpleRouter object
actually your import says otherwise
from app1.urls import router as app1_router
I would change the App1Router to extend either app1.urls.router or DefaultRouter.

Django 404 pages return 200 status code

I'm going through the Django tutorial and am on part 5: Testing. I run into the problem where I'm using the DetailView and ListView "shortcut" views to factor out code (as suggested by the tutorial), but when a 404 page is displayed, a 200 status code is returned instead. Am I doing something wrong? The tutorial says the status code should be 404.
Thanks!
You need to define the Http header to have a 404 status.
return HttpResponse(content=template.render(context), content_type='text/html; charset=utf-8', status=404)
It is important to inform the search engines that the current page is a 404. Spammers sometimes creates lots of urls that could seem that would lead you to some place, but then serves you another content. They frequently make lots of different addresses serve you almost the exact same content. And because it is not user friendly, most SEO guide lines penalize that. So if you have lots of addresses showing the same pseudo-404 content, it could not look good to the crawling systems from the search websites. Because of that you want to make sure that the page you are serving as a custom 404 has a 404 status.
If you are trying to make a custom 404 page, here it is a good way to go:
Into your application's urls.py add:
# Imports
from django.conf.urls.static import static
from django.conf.urls import handler404
from django.conf.urls import patterns, include, url
from yourapplication import views
##
# Handles the URLS calls
urlpatterns = patterns('',
# url(r'^$', include('app.homepage.urls')),
)
handler404 = views.error404
Into your application's views.py add:
# Imports
from django.shortcuts import render
from django.http import HttpResponse
from django.template import Context, loader
##
# Handle 404 Errors
# #param request WSGIRequest list with all HTTP Request
def error404(request):
# 1. Load models for this view
#from idgsupply.models import My404Method
# 2. Generate Content for this view
template = loader.get_template('404.htm')
context = Context({
'message': 'All: %s' % request,
})
# 3. Return Template for this view + Data
return HttpResponse(content=template.render(context), content_type='text/html; charset=utf-8', status=404)
The secret is in the last line: status=404
Hope it helped!
I look forward to see the community inputs to this approach. =)
You can
return HttpResponseNotFound(render_to_string('404.html'))
instead.

In a django web application, how do you give users their own subdomain?

I'm starting a new web app project using Django and Pinax. I want to be able to give my users unique domain names like Wordpress and other sites do : username.wordpress.com. I'm not sure how to approach this with Django, since the url parsing logic (in urls.py) starts with the url AFTER the domain name.
More specifically, there will be multiple groups of users, each group having a unique name. Not sure that makes a difference, but I thought I should mention that.
Is there some way I can manipulate the http request so that the URL looks to Django as if the url were something like www.domain.com/groupname, but still showed in the browser address bar as groupname.domain.com?
You can use some custom middleware to intercept the request and get the subdomain from it. The following code will retrieve the subdomain and redirect to a view by reversing the named url.
Put it in a middleware.py file in your app.
Make sure you set up the middleware in your settings.py file.
Make sure you've named your view in urls.py
middleware.py
from django.http import HttpResponseRedirect
from django.core.urlresolvers import reverse
import re
subdomain_pattern = re.compile('(?P<subdomain>.*?)\..*?')
class SubdomainMiddleware(object):
def process_request(self, request):
match = subdomain_pattern.match(request.get_host())
subdomain = match.group('subdomain')
redirect_url = reverse('groups_detail', args=[subdomain])
return HttpResponseRedirect(redirect_url)
urls.py
from django.conf.urls.defaults import *
urlpatterns = patterns('',
url(r'^groups/(?P<name>.+)/$', 'groups.views.detail', {}, name='group_detail'),
)
Note: this code is untested.
Redirecting can alter the URL's appearance. If you want to avoid this, simply call the associated view, capture its result, and return it in an HttpResponse().
You need to handle this via your webserver. If you have Django urls like...
/users/<username>/
... then use rewrite rules in the webserver to map <username>.domain.com to domain.com/users/<username>/.
If you're using Apache, you can read up here. Otherwise, each webserver has their own conventions but all will support the notion of url rewrites.