Dynamic subdomains and url paths in django project - python

I am trying to implement a dynamic subdomain and urls system in my django project.
Generally, each user has its domain, eg. myusername.example.com
User may define dynamic URLs, for example:
myusername.example.com/something1
myusername.example.com/something2
myusername2.example.com/something1
myusername2.example.com/something2
But I have also my website running on example.com, with urls like example.com/contact, example.com/about-us and so on.
I want to all of these URLs to point to my custom view (class based) where I do some DB queries and return dynamic content. this somethin1/something2 part is fully dynamic, and there may be anything . defined by user.
I've got something like this:
urls.py
from web.views import HomeView, ContactView
urlpatterns = [
path('admin/', admin.site.urls),
path('contact', ContactView.as_view()),
path('', HomeView.as_view()),
re_path('.*', HomeView.as_view())
]
web.views.py
class HomeView(TemplateView):
template_name = 'home.html'
def dispatch(self, request, *args, **kwargs):
SERVICE_DOMAIN = settings.DOMAIN
http_host = str(request.META.get('HTTP_HOST'))
if SERVICE_DOMAIN in http_host:
subdomains = http_host.split(SERVICE_DOMAIN)[0]
subdomain = slugify.slugify(subdomains)
else:
subdomain = False
if subdomain:
print('Our subdomain is {}'.format(subdomain))
kwargs['subdomain'] = subdomain
return CustomUrlView.as_view()(self.request, *args, **kwargs)
if not subdomain:
print('run normally')
return super().dispatch(request, *args, **kwargs)
class CustomUrlView(View):
def dispatch(self, request, *args, **kwargs):
subdomain = kwargs.get('subdomain')
url = request.META.get('PATH_INFO').lower().strip().replace('/', '', 1)
# here I do some queries in DB with my url variable - it has own model etc.
Generally, everything works fine for almost all user defined urls...
Problem is when visitor opens myusername.example.com/contact - its always match the url defined in urls.py and and its not catched by my HomeView.
How can I solve it? I don't want to use any of my class based view's urls defined in urls.py when request comes from a subdomain.

You need to have a two different urls files. One for domain and second for subdomains.
Split domain and subdomain views in two url files. If you have views which works on both e.g. login, create a "common" file and include in both urls.
You can choose which url you will use, so create a middleware and inspect a host request.META.get('HTTP_HOST'). If request comes from subdomain, then simply load appropriated urls request.urlconf = 'path.to_subdomain.urls'
Note:
Be sure that ROOT_URLCONF in your settings.py point to the "domain' urls. Also, in your middleware you should inspect does subdomain exists and return 404 if it doesn't exist.

Related

Django Redirect by Name - No Reverse Match Error

My django project name is 'exercises' and the app name is 'practice'. This basic application allows a user can navigate to different pages with a button click. The reference to each page uses the following href:
{% url 'practice:redirector' project.pk %}
Where practice:redirector points to my app (practice) url path named redirector. It is parameterized with the primary key of the page which I am trying to redirect to. See below
from django.urls import path
from practice import views
app_name = "practice"
urlpatterns = [
path('', views.all_sites, name="all_sites"),
path('<int:pk>', views.redirector, name='redirector'),
path('clickToAddItems', views.clickToAddItems, name='clickToAddItems'),
path('displayEvens', views.displayEvens, name='displayEvens'),
path('displayMousePosition', views.displayMousePosition, name='displayMousePosition'),
path('luckySevens', views.luckySevens, name='luckySevens'),
]
This url points to the views within practice. There, I have a view set up for each page that corresponds to the name that the page will display. I also have a re director view that uses the pk passed in the url to pull the relevant page from my database and use the name as the argument in the redirect() shortcut:
from django.shortcuts import render, redirect
from practice.models import Project
app_name = "practice"
# Create your views here.
# Main list of javascript sites
def all_sites(request):
# query my project model to return all site objects
projects = Project.objects.all()
# return a rendered template with the site information passed into it
return render(request, 'practice/all_sites.html', {'projects':projects})
def clickToAddItems(request):
return render(request, 'practice/clickToAddItems')
def displayEvens(request):
return render(request, 'practice/displayEvens')
def displayMousePosition(request):
return render(request, 'practice/displayMousePosition')
def luckySevens(request):
return render(request, 'practice/luckySevens')
def redirector(request, pk):
project = Project.objects.get(pk=pk)
site_name=project.title
return redirect(site_name, permanent=True)
I am expecting django to use the redirector view to redirect depending on the provided name. Using the debugger, I can see that the database is correctly queried and the correct name is passed to the redirect with each url:
C:\Users\User\my_repository\my_repository\Dev\small-projects\practice\views.py, line 29, in redirector
def luckySevens(request):
return render(request, 'practice/luckySevens')
def redirector(request, pk):
project = Project.objects.get(pk=pk)
site_name=project.title
return redirect(site_name, permanent=True)
# Local vars:
#(These are the local variables tracked by the debugger, not project code)
pk = 1
project = <Project: Project object (1)>
request = <WSGIRequest: GET '/practice/1'>
site_name = 'clickToAddItems'
However, I get a NoReverseMatch error because django is using the project url file instead of my application url file to find the corresponding view.
C:\Users\User\my_repository\my_repository\Dev\small-projects\venv\lib\site-packages\django\urls\base.py, line 88, in reverse
else:
raise NoReverseMatch("%s is not a registered namespace" % key)
if ns_pattern:
resolver = get_ns_resolver(
ns_pattern, resolver, tuple(ns_converters.items())
)
return resolver._reverse_with_prefix(view, prefix, *args, **kwargs) …
reverse_lazy = lazy(reverse, str)
def clear_url_caches():
# Local vars:
#(These are the local variables tracked by the debugger, not project code)
args = []
current_app = None
current_path = None
kwargs = {}
ns_converters = {}
ns_pattern = ''
path = []
prefix = '/'
resolved_path = []
resolver = <URLResolver 'exercises.urls' (None:None) '^/'>
urlconf = 'exercises.url'
view = 'clickToAddItems'
viewname = 'clickToAddItems'
I need the resolver to look at 'practice.urls' instead of 'exercises.urls'. How can I do this? I have tried parameterizing urlconf=practice.urls within the redirect shortcut of the redirector view, but that does not work. Any and all feed back is very much appreciated! Thank you :)
if you want to redirect to
site_name = 'clickToAddItems'
as in your debug example, you should name the app:
site_name = 'practice:clickToAddItems'

DRF (django-rest-framework) action decorator not working

I have this class view, that works perfectly for creating and listing objects of SiteGroup:
But I need a method to perform several operations on a single SiteGroup object, and objects associated with them. Therefore, I have tried to create a method, decorated with #action (as suggested by the docs).
According to the docs, this will autogenerate the intermediate url. Nevertheless, it doesn't work.
When I try to access (given that 423 is an existing SiteGroup Object):
http://127.0.0.1:8000/api/site-groups/423/replace_product_id/?product_id=0x345
the url is not found.
I also tried generating myself the URL in the urls.py, with no luck.
Can someone tell me where the problem is? I've browsed through all the docs, and found no clue. Thanks a lot.
class SiteGroupDetail(generics.ListCreateAPIView):
queryset = SiteGroup.objects.all()
parser_classes = (MultiPartParser, FormParser, JSONParser)
serializer_class = SiteGroupSerializer
authentication_classes = (authentication.TokenAuthentication,)
#action(detail=True, methods=['post'], url_path='replace_product_id', permission_classes=[IsSuperUser], url_name='replace_product_id')
def replace_product_id(self, request, pk=None, device_type=None):
serializer = SiteGroupSerializer(data=request.data)
product_id = self.request.query_params.get('product_id', None)
print("replace_product", product_id, device_type, pk, flush=True)
return Response({"hello":product_id})
My urls.py
from django.conf.urls import url, include
from api import views, routers
router = routers.SimpleRouter()
router.register(r'users', views.UserViewSet)
router.register(r'groups', views.GroupViewSet)
urlpatterns = [
url(r'^', include(router.urls)),
enter code here
url(r'^site-groups/', views.SiteGroupDetail.as_view()),
url(r'^site-groups/(?P<pk>[0-9]+)/$', views.SiteGroupDetail.as_view()),
]
For one thing the router should be calling
super(OptionalSlashRouter, self).__init__()
What you have now calls __init__ of SimpleRouter's parent, skipping the logic in SimpleRouter.__init__
Change that and see if it starts working
Actually as you're using python 3 it could be just
super ().__init__()

Django i18n particular behavior setup

I need django i18n to work like this:
site.com/lang/ set the session language to lang, writes it to the cookie and redirects user to site.com. If it's a first-time visitor and prefix is not specified, default lang is shown.
Sorry for such a basic question,example of urls.py and middlewares maybe would be extremely appreciated.
Django comes with a pretty similar solution to what you are trying to do. It's called The set_language Redirect view. The difference is that it expects the language as a POST parameter. You might want to consider using this versus a custom solution.
If that's not what you're looking for, you could write your own Redirect View, that would set the language to lang and redirect to site.com
class SetLanguageView(RedirectView):
url = reverse('home')
def get(self, request, *args, **kwargs):
response = super(self, SetLanguageView).get(request, *args, **kwargs)
lang = kwargs.get('lang')
if lang:
# To set the language for this session
request.session[settings.LANGUAGE_SESSION_KEY] = lang
# To set it as a cookie
response.set_cookie(settings.LANGUAGE_COOKIE_NAME, lang,
max_age=settings.LANGUAGE_COOKIE_AGE,
path=settings.LANGUAGE_COOKIE_PATH,
domain=settings.LANGUAGE_COOKIE_DOMAIN)
return response
And in urls.py you would have something like
urlpatterns = patterns('',
url(r'^(?P<lang>\w+)/$', RedirectView.as_view(), name='lang_redirect'),

Django Admin Custom View URL not being recognized

I am using Django 1.8 on Python 3.4.3
I have a Model called FormUpload and I am adding a ModelAdmin for the same in admins.py in the same application "mca".
#admin.register(FormUpload)
class FormUploadAdmin(admin.ModelAdmin):
def upload_form(self, request):
context = dict(
# Include common variables for rendering the admin template.
self.admin_site.each_context(request),
# Anything else we want in the context...
)
return TemplateResponse(request, "admin/formupload.html", context)
def get_urls(self):
urls = super(FormUploadAdmin, self).get_urls()
upload_admin_urls = [
url(r'^upload/$', self.admin_site.admin_view(self.upload_form)),
]
logger.debug(upload_admin_urls)
return upload_admin_urls + urls
This should have been available at /admin/mca/upload/ . However when I go the URL I get a 404 saying the current URL doesn't match anything
Here is the output of the debug
RegexURLPattern None ^upload/$
(Removed few things that will make the display weird for the debug).
Please notice the None. If the other URLs are listed out there is a method name where the None is.
What am I doing wrong here ? I am following the approach suggested in django documentation - https://docs.djangoproject.com/en/1.8/ref/contrib/admin/#modeladmin-methods

django urls without a trailing slash do not redirect

I've got two applications located on two separate computers. On computer A, in the urls.py file I have a line like the following:
(r'^cast/$', 'mySite.simulate.views.cast')
And that url will work for both mySite.com/cast/ and mySite.com/cast. But on computer B I have a similar url written out like:
(r'^login/$', 'mySite.myUser.views.login')
For some reason on computer B the url mySite.com/login/ will work but mySite.com/login will hang and won't direct back to mySite.com/login/ like it will on computer A. Is there something I missed? Both url.py files look identical to me.
Or you can write your urls like this:
(r'^login/?$', 'mySite.myUser.views.login')
The question sign after the trailing slash makes it optional in regexp. Use it if for some reasons you don't want to use APPEND_SLASH setting.
check your APPEND_SLASH setting in the settings.py file
more info in the django docs
This improves on #Michael Gendin's answer. His answer serves the identical page with two separate URLs. It would be better to have login automatically redirect to login/, and then serve the latter as the main page:
from django.conf.urls import patterns
from django.views.generic import RedirectView
urlpatterns = patterns('',
# Redirect login to login/
(r'^login$', RedirectView.as_view(url = '/login/')),
# Handle the page with the slash.
(r'^login/', "views.my_handler"),
)
I've had the same problem too. My solution was put an (|/) before the end line of my regular expression.
url(r'^artists/(?P[\d]+)(|/)$', ArtistDetailView.as_view()),
Append slash without redirect, use it instead of CommonMiddleware in settings, Django 2.1:
MIDDLEWARE = [
...
# 'django.middleware.common.CommonMiddleware',
'htx.middleware.CommonMiddlewareAppendSlashWithoutRedirect',
...
]
Add to your main app directory middleware.py:
from django.http import HttpResponsePermanentRedirect, HttpRequest
from django.core.handlers.base import BaseHandler
from django.middleware.common import CommonMiddleware
from django.conf import settings
class HttpSmartRedirectResponse(HttpResponsePermanentRedirect):
pass
class CommonMiddlewareAppendSlashWithoutRedirect(CommonMiddleware):
""" This class converts HttpSmartRedirectResponse to the common response
of Django view, without redirect.
"""
response_redirect_class = HttpSmartRedirectResponse
def __init__(self, *args, **kwargs):
# create django request resolver
self.handler = BaseHandler()
# prevent recursive includes
old = settings.MIDDLEWARE
name = self.__module__ + '.' + self.__class__.__name__
settings.MIDDLEWARE = [i for i in settings.MIDDLEWARE if i != name]
self.handler.load_middleware()
settings.MIDDLEWARE = old
super(CommonMiddlewareAppendSlashWithoutRedirect, self).__init__(*args, **kwargs)
def process_response(self, request, response):
response = super(CommonMiddlewareAppendSlashWithoutRedirect, self).process_response(request, response)
if isinstance(response, HttpSmartRedirectResponse):
if not request.path.endswith('/'):
request.path = request.path + '/'
# we don't need query string in path_info because it's in request.GET already
request.path_info = request.path
response = self.handler.get_response(request)
return response
In some cases, we have issues when some of our users call API with different endings. Usually, our users use Postman for that and are not worried about slash at the endpoint. As result, we receive issue requests in support, because users forgot to append a slash / at the end of POST requests.
We solved it by using a custom middleware that works for us in Django 3.2+ and Django 4.0+. After that, Django may handle any POST/PUT/DELETE requests to your API with slash or without them. With this middleware unneeded to change APPEND_SLASH property in settings.py
So, in the settings.py need to remove your current 'django.middleware.common.CommonMiddleware' and insert new middleware. Make sure, you change your_project_name in my example below on your real project name.
MIDDLEWARE = [
...
# 'django.middleware.common.CommonMiddleware',
'your_project_name.middleware.CommonMiddlewareAppendSlashWithoutRedirect',
...
]
Add to your main app directory middleware.py:
from django.http import HttpResponsePermanentRedirect, HttpRequest
from django.core.handlers.base import BaseHandler
from django.middleware.common import CommonMiddleware
from django.utils.http import escape_leading_slashes
from django.conf import settings
class HttpSmartRedirectResponse(HttpResponsePermanentRedirect):
pass
class CommonMiddlewareAppendSlashWithoutRedirect(CommonMiddleware):
""" This class converts HttpSmartRedirectResponse to the common response
of Django view, without redirect. This is necessary to match status_codes
for urls like /url?q=1 and /url/?q=1. If you don't use it, you will have 302
code always on pages without slash.
"""
response_redirect_class = HttpSmartRedirectResponse
def __init__(self, *args, **kwargs):
# create django request resolver
self.handler = BaseHandler()
# prevent recursive includes
old = settings.MIDDLEWARE
name = self.__module__ + '.' + self.__class__.__name__
settings.MIDDLEWARE = [i for i in settings.MIDDLEWARE if i != name]
self.handler.load_middleware()
settings.MIDDLEWARE = old
super(CommonMiddlewareAppendSlashWithoutRedirect, self).__init__(*args, **kwargs)
def get_full_path_with_slash(self, request):
""" Return the full path of the request with a trailing slash appended
without Exception in Debug mode
"""
new_path = request.get_full_path(force_append_slash=True)
# Prevent construction of scheme relative urls.
new_path = escape_leading_slashes(new_path)
return new_path
def process_response(self, request, response):
response = super(CommonMiddlewareAppendSlashWithoutRedirect, self).process_response(request, response)
if isinstance(response, HttpSmartRedirectResponse):
if not request.path.endswith('/'):
request.path = request.path + '/'
# we don't need query string in path_info because it's in request.GET already
request.path_info = request.path
response = self.handler.get_response(request)
return response
This answer may look similar to Max Tkachenko answer. But his code didn't work for me in the latest versions of Django.
I've had the same problem. In my case it was a stale leftover from some old version in urls.py, from before staticfiles:
url(r'^%s(?P<path>.*)$' % settings.MEDIA_URL.lstrip('/'),
'django.views.static.serve',
kwargs={'document_root': settings.MEDIA_ROOT}),
MEDIA_URL was empty, so this pattern matched everything.

Categories