I have a RoutingUrl model which describes all the urls used on my site with the view (foreign key to the View model) that has to manage the url and some other routing information. The urls are continuously growing in size, and should also support the redirect. The models are more or less the following:
class RoutingUrl(models.Model):
url = models.CharField(unique=True, verbose_name='routing url')
crc_checksum = models.IntegerField(editable=False)
redirect_to = models.ForeignKey('RoutingUrl', related_name='redirect_from', blank=True, null=True)
view = models.ForeignKey(View, related_name='routing_urls')
content_type = models.ForeignKey(ContentType)
object_id = models.PositiveIntegerField()
content_object = GenericForeignKey('content_type', 'object_id')
class View(models.Model):
name = models.CharField()
The RoutingUrl has also a generic foreign key with the model containing information about the page to be rendered (different models has to be supported, this is the reason for the generic foreign key).
Now the question is: how to implement such a dynamic routing into django ? My feeling is that I have two options:
I could create a middleware which will take care of dispatching the request to the right view by implementing the process_request method (and so before the urlpatterns are checked). Obviously such a middleware should be positioned at the bottom of the middleware stack, in order to maintain the functionality of other middlewares. This solution will thus bypass the Django routing system.
Another option could be to add a single catch all urlpattern that matches everything, and then just write my own handler/dispatcher as a view. That handler implement the routing process and so will call the appropriate view and return its HttpResponse
Could you suggest me which of the two options is the best to implement such a routing ? Of course if there is a third, better option, don't hesitate to suggest it to me.
Investigating a bit into the django middleware hooks, it become evident that the process_request is not the best candidate for implementing the routing functionality. Indeed, from the documentation:
It should return either None or an HttpResponse object. If it returns
None, Django will continue processing this request, executing any
other process_request() middleware, then, process_view() middleware,
and finally, the appropriate view. If it returns an HttpResponse
object, Django won’t bother calling any other request, view or
exception middleware, or the appropriate view; it’ll apply response
middleware to that HttpResponse, and return the result.
So an HttpResponse will break the middleware stack functionalities. It's more or less the same for the process_view, which will avoid the calls of the exception middlewares. At this point it seems more smart to adopt the second option...
The django-cms plugin confirm this intuition, as you could see from the source code of the urlpatterns definition:
from django.conf import settings
from django.conf.urls import url
from cms.apphook_pool import apphook_pool
from cms.appresolver import get_app_patterns
from cms.views import details
# This is a constant, really, but must live here due to import order
SLUG_REGEXP = '[0-9A-Za-z-_.//]+'
if settings.APPEND_SLASH:
regexp = r'^(?P<slug>%s)/$' % SLUG_REGEXP
else:
regexp = r'^(?P<slug>%s)$' % SLUG_REGEXP
if apphook_pool.get_apphooks():
# If there are some application urls, use special resolver,
# so we will have standard reverse support.
urlpatterns = get_app_patterns()
else:
urlpatterns = []
urlpatterns.extend([
url(regexp, details, name='pages-details-by-slug'),
url(r'^$', details, {'slug': ''}, name='pages-root'),
])
Related
There is a class in admin.py:
class NewsAdmin(TranslationAdmin):
form = NewsForm
list_display = ('title', 'date' 'show_url')
def show_url(self, obj):
return format_html("<a href='http://www.host.ru/news/{url}' target='_blank'>view</a>", url=obj.id)
show_url.short_description = "View"
Need a link http://www.host.ru replace with a relative one, something like this: protocol + domain name + news/{url}
How can this be done?
I think you are better off making it hardcoded value in your settings, like BASEURL = 'http://www.host.ru'
then having different settings.py for production and development (dev with BASEURL = 'https://localhost:8080' for testing)
and then you can fetch it with:
def show_url(self, obj):
from django.conf import settings
return format_html("<a href='{base}/news/{url}' target='_blank'>view</a>", base=settings.BASEURL, url=obj.id)
# or use reverse so it's dynamically linked to your urls.py (this is what I do)
def show_url_with_reverse(self, obj):
from django.conf import settings
from django.urls import reverse
return format_html("<a href='{base}{url}' target='_blank'>view</a>",
base=settings.BASEURL,
url=reverse('myviewname', args=[obj.id])
)
This isn't the answer you wanted, but I'm throwing it in as a way. I hope someone smarter than me has found a better way, but this is a fallback.
Reasoning
This is all moot because you don't have access to the request object inside that method.
I've found that you need the request object to dynamically get the host, the www.host.ru part, with request.get_host().
But that still leads the http:// and https://, which is not available anywhere (that I've found), so I have that hardcoded in my settings.py.
So either way something will have have to be hardcoded, so might as well be the entire url. It's gross, I hate it, but w/e it gets the job done
I'm using REST in Django, And I couldn't understand what is the main difference between classic URL and instantiating DefaultRouter() for registering URL by ViewSet.
I have a model:
class Article(models.Model):
title = models.CharField()
body = models.TextField()
author = models.ForeignKey()
Serializing model like this:
from blog.models import Article
class ArticleSerializer(serializers.ModelSerializer):
class Meta:
model = Article
fields = ['title', 'body', 'author']
View Class:
from blog.models import Article
from rest_framework import viewsets
from .serializers import ArticleSerializer
class ArticleViewSet(viewsets.ModelViewSet):
serializer_class = ArticleSerializer
queryset = Article.objects.all()
and URLS:
router = DefaultRouter()
router.register(r'articles', ArticleViewSet)
urlpatterns = [
path('', include(router.urls)),
]
Is it possible to use classic URL in URLS.py instead of instantiating the object for a ViewSet like this:
urlpatterns = [
path('api/', 'views.someAPI'),
]
I just know HTTP method in ViewSet translate methods to retrieve, list and etc...
The Question is can we use traditional(Classic) URL style in this situation, Should we ?
Thanks for your help.
Well, in a nutshell as a django developer it is notorious how it is hard to deal with normal urls in django in some cases. Every now and again we get confused with the id type of the detail page that in some case are strings or integers with its regex, and so on.
For example:
urlpatterns = [
url(r'^(?P<content_type_name>[a-zA-z-_]+)$', views.content_type, name = 'content_type'),
]
# or
urlpatterns = [
url(r'^(?P<content_type_name>comics|articles|videos)$', views.content_type, name='content_type'),
]
Not mentioning that in almost every case its needed to have two urls like:
URL pattern: ^users/$ Name: 'user-list'
URL pattern: ^users/{pk}/$ Name: 'user-detail'
THE MAIN DIFFERENCE
However, using DRF routers the example above is done automatically:
# using routers -- myapp/urls.py
router.register(r"store", StoreViewSet, basename="store")
How django will understand it:
^store/$ [name='store-list']
^store\.(?P<format>[a-z0-9]+)/?$ [name='store-list']
^store/(?P<pk>[^/.]+)/$ [name='store-detail']
^store/(?P<pk>[^/.]+)\.(?P<format>[a-z0-9]+)/?$ [name='store-detail']
See how much job and headache you have saved with a line of code only?
To contrast, according to DRF documentation the routers is a type of standard to make it easy to declare urls. A pattern brought from ruby-on-rails.
Here is what the documentation details:
Resource routing allows you to quickly declare all of the common
routes for a given resourceful controller. Instead of declaring
separate routes for your index... a resourceful route declares them in
a single line of code.
— Ruby on Rails Documentation
Django rest framework documentation:
Some Web frameworks such as Rails provide functionality for
automatically determining how the URLs for an application should be
mapped to the logic that deals with handling incoming requests.
REST framework adds support for automatic URL routing to Django, and
provides you with a simple, quick and consistent way of wiring your
view logic to a set of URLs.
For more details follow the django rest framework documentation.
I have recently inherited an API built with Django and DRF. I need to add some endpoints to the API but have never worked with Django or DRF before so I am trying to come up to speed as quickly as possible.
I am wondering how to do custom endpoints that don't just translate data too/from the backend database. A for instance might be an endpoint that reads data from the DB then compiles a report and returns it to the caller in JSON. But I suppose that right now the simplest method would be one that when the endpoint is hit just prints 'Hello World' to the log and returns a blank page.
I apologize if this seems basic. I've been reading through the docs and so far all I can see is stuff about serializers when what I really need is to be able to call a custom block of code.
Thanks.
if you want your REST endpoint to have all: GET, POST, PUT, DELETE etc. functionality then you have to register a route in your urls.py:
urls.py:
from rest_framework import routers
from django.urls import path, include
from . import views
router = routers.DefaultRouter()
router.register(r'hello', views.HelloWorldViewSet)
urlpatterns = [
# Wire up our API using automatic URL routing.
# rest_framework api routing
path('api/', include(router.urls)),
# This requires login for put/update while allowing get (read-only) for everyone.
path('api-auth/', include('rest_framework.urls', namespace='rest_framework'))
]
now the url: /hello/ points to the HelloWorldViewSet.
in your views.py add the HelloWorldViewSet that will inherits from the rest_framework.viewsets.ViewSet class. You can override the ViewSet default class behavior by defining the following "actions": list(), create(), retrieve(), update(), partial_update(), destroy(). For displaying "hello world" on GET you only need to override list():
so in your views.py:
from rest_framework import viewsets
from rest_framework.response import Response
class HelloWorldViewSet(viewsets.ViewSet):
def list(self, response):
return Response('Hello World')
So, in your more advanced list() function you have to interact with the database, to retrieve the data you want, process it and create the report as a json serializable dictionary and return it as a Response object.
If you don't want to override the standard list action, you could instead add a new action to the HelloWorldViewSet let's call it report:
so in your views.py:
from rest_framework import viewsets
from rest_framework.response import Response
from rest_framework.decorators import action
class HelloWorldViewSet(viewsets.ViewSet):
#action(detail=False)
def report(self, request, **kwargs):
return Response('Hello World')
I hope this is what you were looking for.
Note that you don't need django-rest-framework if you are not interested in POST, PUT, PATCH, DELETE, etc... you can simply add a path to your urls.py that points to a Django view function that returns a Django JsonResponse object containing your report.
One good option for you would be DRF actions. Docs here
The action allows you to choose a relevant view for what you wanna do, so you can just pop things into the API you inherited. No additional setup needed, they show up next to the regular routes.
Hello! Please tell me how to organize a redirect correctly.
There is an old version of the site and a new one. In the old version (another CMS, not Django) the objects have their own URL, in the new revised scheme, and the objects have a different URL.
In each object on the new site, there is a completed field with the old URL. In model.py it looks like this:
old_url = models.CharField('Old URL', blank=True, max_length=100)
I specifically moved the old url to a separate field. Or was it not necessary to do this?
Question. How to set up a redirect correctly, so that after going to the object using the old URL, the site visitor will be redirected to the new URL of this object?
IMHO, I don't think writting old_url for each and every object is pretty inefficient. Instead you can implement a custom 404 view, and handle the redirection there.
I think you can create some regex or plain url maps to new url and redirect accordingly.
import re
from django.http import HttpResponseNotFound
OLD_URL_MAP = { 'old_url_regex': 'new_url_path'}
def handler404(self, request):
for old_re, new_url in OLD_URL_MAP.items():
if re.match(old_re, request.path):
return redirect(new_url, request.resolver_match.kwargs)
return HttpResponseNotFound('not found')
# inside urls.py
handler404 = 'myapp.views.handler404'
Here I have used a map hard coded in python, you can create a model for that as well.
Update
A costly solution is to use middleware. You can try like this:
import re
from django.urls import resolve
OLD_URL_MAP = { 'old_url_regex': 'new_url_path'}
class RerouteMiddleware:
def __init__(self, get_response):
self.get_response = get_response
def __call__(self, request):
try:
resolve(request.path_info) # trying to find if the current url exists in your django project. if not, it will throw exception.
except:
for old_re, new_url in OLD_URL_MAP.items(): # iterating through urls
if re.match(old_re, request.path):
return redirect(new_url, request.resolver_match.kwargs)
response = self.get_response(request)
return response
And add that middleware at the bottom of MIDDLEWARE settings.
FYI, its a regex based solution, assuming those urls are dynamic. Instead you can use plain text urls, but its up to you.
Use redirect() from django.shortcuts [the same library from where you import render]. Also, assuming, that the old_url contains only the relative url.
from django.shortcuts import render, redirect
def someView(request):
q = ~some queryset returning the current object~
current_url = request.get_full_path().strip("http://www.example.com/")
if q.old_url == current_url:
redirect(q.new_url)
else:
pass
Remember, redirect() returns an HttpResponse.
I am creating REST based APIs for an app using Tastypie with Django. The problem is default API url in Tastypie contains version info in url patterns i.e.
http://lx:3001/api/v1/vservers/?username=someuser&api_key=someapikey
I want my url to be free from API version info like this:
http://lx:3001/api/vservers/?username=someuser&api_key=someapikey
urls.py
v1_api = Api()
v1_api.api_name = ''
v1_api.register(UserResource())
...
url(r'^api/', include(v1_api.urls)),
I am overwriting api_name with an empty string still
http://lx:3001/api/vservers/?username=someuser&api_key=someapikey does not work.
How can I get rid of the version info altogether?
Thanks..
Subclass Api and override urls to remove all the api_name-related bits:
class MyApi(Api):
#property
def urls(self):
"""
Provides URLconf details for the ``Api`` and all registered
``Resources`` beneath it.
"""
pattern_list = [
url(r"^%s$" % trailing_slash(), self.wrap_view('top_level'), name="api_top_level"),
]
for name in sorted(self._registry.keys()):
pattern_list.append((r"^/", include(self._registry[name].urls)))
urlpatterns = self.override_urls() + patterns('',
*pattern_list
)
return urlpatterns
Although tastypie makes supplying api_name optional, failing to provide one simply defaults api_name to "v1". This behavior can be modified by subclassing Api and overriding the urls property within api.py to behave independently of api_name. To achieve the desired URLconf, however, there's still one correction to #dokkaebi's solution worth noting:
pattern_list.append((r"^/", include(self._registry[name].urls)))
should instead read:
pattern_list.append((r'', include(self._registry[name].urls)))
in order to avoid the dreaded // that would direct your clients to
http://lx:3001/api//vservers/?username=someuser&api_key=someapikey
in place of
http://lx:3001/api/vservers/?username=someuser&api_key=someapikey
as intended.
For convenience, I've included the modified code below.
Solution
class MyApi(Api):
"""
An API subclass that circumvents api_name versioning.
"""
#property
def urls(self):
"""
Provides URLconf details for the ``Api`` and all registered
``Resources`` beneath it.
"""
pattern_list = [
url(r"^%s$" % trailing_slash(), self.wrap_view('top_level'), name="api_top_level"),
]
for name in sorted(self._registry.keys()):
pattern_list.append((r'', include(self._registry[name].urls)))
urlpatterns = self.override_urls() + patterns('',
*pattern_list
)
return urlpatterns
Convention
One of Django’s core philosophies is that URLs should be beautiful; a clean, elegant URL scheme is an important detail in any high-quality Web application. With respect to the validity of this approach, using a custom request header or an accept header will get the versioning job done without convoluting the scheme with the (subjectively) ugly v1/. That is not to say that the URL versioning strategy is without its share of caveats; however, it's quick to implement and predictable in its response.