Django: prefix/postfix language slug in i18n_urls - django

I have a django-cms site, that uses i18n_patterns in urls.py. This works, urls are built like /lang/here-starts-the-normal/etc/.
Now, I would like to have urls like this: /prefix-lang/here-starts.... As there will be a couple of country specific domains, this wille be like /ch-de/here-... for Switzerland/.ch domain, /us-en/here-starts.... for the states, and some more. So, when the url would be /ch-de/..., the LANGUAGE would still be de. Hope this is clear?
As the content is filled with existing LANGUAGES=(('de', 'DE'), ('en', 'EN'), ...), I cannot change LANGUAGES for every domain - no content would be found in the cms, modeltranslation, only to mention those two.
How can I prefix the language slug in i18n_patterns? Is it possible at all?

I think a way without hacking Django too much would be to use URL rewrite facility provided by the webserver you run, for example, for mod_wsgi you can use mod_rewrite, similar facility exists also for uWSGI.
You may need to also post-process the output from Django to make sure that any links are also correctly re-written to follow the new schema. Not the cleanest approach but seems doable.

Working example, though the country/language order is reversed (en-ch instead of ch-en), to have it as django expects it when trying to find a language (ie, setting language to "en-ch", it will find "en", if available).
This solution involves a modified LocaleMiddleware, i18n_patterns, LocaleRegexResolver. It supports no country, or a 2 char country code, setup with settings.SITE_COUNTRY. It works by changing urls to a lang-country mode, but the found language code in middleware will still be language only, 2 chars, and work perfectly with existing LANGUAGES, that contain 2 chars language codes.
custom_i18n_patterns.py - this just uses our new resolver, see below
from django.conf import settings
from ceco.resolvers import CountryLocaleRegexURLResolver
def country_i18n_patterns(*urls, **kwargs):
"""
Adds the language code prefix to every URL pattern within this
function. This may only be used in the root URLconf, not in an included
URLconf.
"""
if not settings.USE_I18N:
return list(urls)
prefix_default_language = kwargs.pop('prefix_default_language', True)
assert not kwargs, 'Unexpected kwargs for i18n_patterns(): %s' % kwargs
return [CountryLocaleRegexURLResolver(list(urls), prefix_default_language=prefix_default_language)]
resolvers.py
import re
from django.conf import settings
from django.urls import LocaleRegexURLResolver
from modeltranslation.utils import get_language
class CountryLocaleRegexURLResolver(LocaleRegexURLResolver):
"""
A URL resolver that always matches the active language code as URL prefix.
extended, to support custom country postfixes as well.
"""
#property
def regex(self):
language_code = get_language() or settings.LANGUAGE_CODE
if language_code not in self._regex_dict:
if language_code == settings.LANGUAGE_CODE and not self.prefix_default_language:
regex_string = ''
else:
# start country changes
country_postfix = ''
if getattr(settings, 'SITE_COUNTRY', None):
country_postfix = '-{}'.format(settings.SITE_COUNTRY)
regex_string = '^%s%s/' % (language_code, country_postfix)
# end country changes
self._regex_dict[language_code] = re.compile(regex_string, re.UNICODE)
return self._regex_dict[language_code]
middleware.py - only very few lines changed, but had to replace the complete process_response.
from django.middleware.locale import LocaleMiddleware
from django.conf import settings
from django.conf.urls.i18n import is_language_prefix_patterns_used
from django.http import HttpResponseRedirect
from django.urls import get_script_prefix, is_valid_path
from django.utils import translation
from django.utils.cache import patch_vary_headers
class CountryLocaleMiddleware(LocaleMiddleware):
"""
This is a very simple middleware that parses a request
and decides what translation object to install in the current
thread context. This allows pages to be dynamically
translated to the language the user desires (if the language
is available, of course).
"""
response_redirect_class = HttpResponseRedirect
def process_response(self, request, response):
language = translation.get_language()
language_from_path = translation.get_language_from_path(request.path_info)
urlconf = getattr(request, 'urlconf', settings.ROOT_URLCONF)
i18n_patterns_used, prefixed_default_language = is_language_prefix_patterns_used(urlconf)
if (response.status_code == 404 and not language_from_path and
i18n_patterns_used and prefixed_default_language):
# Maybe the language code is missing in the URL? Try adding the
# language prefix and redirecting to that URL.
# start country changes
language_country = language
if getattr(settings, 'SITE_COUNTRY', None):
language_country = '{}-{}'.format(language, settings.SITE_COUNTRY)
language_path = '/%s%s' % (language_country, request.path_info)
# end country changes!
path_valid = is_valid_path(language_path, urlconf)
path_needs_slash = (
not path_valid and (
settings.APPEND_SLASH and not language_path.endswith('/') and
is_valid_path('%s/' % language_path, urlconf)
)
)
if path_valid or path_needs_slash:
script_prefix = get_script_prefix()
# Insert language after the script prefix and before the
# rest of the URL
language_url = request.get_full_path(force_append_slash=path_needs_slash).replace(
script_prefix,
'%s%s/' % (script_prefix, language_country),
1
)
return self.response_redirect_class(language_url)
if not (i18n_patterns_used and language_from_path):
patch_vary_headers(response, ('Accept-Language',))
if 'Content-Language' not in response:
response['Content-Language'] = language
return response

Related

Django url path converter not working in production

I'm using path converter in my django app like so:
# urls.py
from . import views
from django.urls import path
urlpatterns = [
path('articles/<str:collection>', views.ArticleView),
]
# views.py
#login_required
def ArticleView(request, collection):
print(collection)
if collection == "None":
articles_query = ArticleModel.objects.all()
...
This works fine in development for a url suck as : http://localhost:8000/articles/My Collection which gets encoded to http://localhost:8000/articles/My%20Collection, and is decoded properly in the ArticleView. However, in development, I have to edit the view like so to get it to work:
# views.py
import urllib.parse
#login_required
def ArticleView(request, collection):
collection = urllib.parse.unquote(collection)
print(collection)
if collection == "None":
articles_query = ArticleModel.objects.all()
...
Otherwise, the print(collection) shows My%20Collection and the whole logic in the rest of the view fails.
requirements.txt
asgiref==3.2.10
Django==3.1.1
django-crispy-forms==1.9.2
django-floppyforms==1.9.0
django-widget-tweaks==1.4.8
lxml==4.5.2
Pillow==7.2.0
python-pptx==0.6.18
pytz==2020.1
sqlparse==0.3.1
XlsxWriter==1.3.3
pymysql
What am I doing wrong here?
Thanks in advance!
The URL is being urlencoded which encodes spaces as %20. There are a number of other encodings. As you've discovered you need to decode that parameter in order to compare it to what you'd expect. As you've likely realized, if you have a value that actually wants The%20News and not The News, you have no recourse. To handle this people will create a slug field. Django has a model field for this in the framework.
This is typically a URL-friendly, unique value for the record.
Assuming you add a slug = models.SlugField() to ArticleModel, your urls and view can change into:
urlpatterns = [
# Define a path without a slug to identify the show all code path and avoid the None sentinel value.
path('articles', views.ArticleView, name='article-list'),
path('articles/<slug:slug>' views.ArticleView, name='article-slug-list'),
]
#login_required
def ArticleView(request, slug=None):
articles_query = ArticleModel.objects.all()
if slug:
articles_query = articles_query.filter(slug=slug)

How do I change the local path of a language in Django?

I have a web set up with 3 languages (['es', 'en', 'it']) ​​in Angular that works with the Django server.
Django by default has the English local path defined as /en, I want the url of the English configuration to be '/us' (by default it is '/ en'), I just want to change it for English, for 'es' or 'it' as it comes by default is fine.
I want the URL to look like this myurl.com/us in English, how would you recommend me to make this change?
The structure is the following:
-apps
--webapp
---templates
----webapp
-----en (inside index.html)
-----es (inside index.html)
-----it (inside index.html)
-conf
--settings.py
-middleware
--locale.py
conf.settings.py have this language configuration
LANGUAGES = (
('es', _('Spanish')),
('it', _('Italian')),
('en', _('English')),
)
LANGUAGE_CODE = 'es'
LANGUAGE_CODES = [language[0] for language in LANGUAGES]
Additionally I have configured a middleware to recognize the user's location and place the corresponding language code in the URL
from django.conf import settings
from .utils.geolocation import get_language_by_ip
cookie_name = settings.LANGUAGE_COOKIE_NAME
class LocalizationMiddleware(object):
def get_language_cookie(self, request):
return request.COOKIES.get(cookie_name)
def set_language_cookie(self, request, value):
request.COOKIES[cookie_name] = value
def get_i18n_url_language(self, request):
url = request.path.split('/')
if len(url) > 1 and len(url[1].split('-')[0]) == 2:
return url[1]
return None
def process_request(self, request):
language = self.get_i18n_url_language(request)
if language is not None and language not in settings.LANGUAGE_CODES:
return
if language is None:
language = self.get_language_cookie(request)
if language is None:
language = get_language_by_ip(request)
if language is None:
language = settings.LANGUAGE_CODE
self.set_language_cookie(request, language)
return None
def process_response(self, request, response):
response.set_cookie(cookie_name, request.COOKIES.get(cookie_name, settings.LANGUAGE_CODE))
return response
This all works fine, however I want the url for the English language to change from .../en to .../us

Embedding Video File in Django Site

I have a Django site that I'm creating, and I want some of the pages to have videos embedded in them. These videos aren't part of a model. I just want to be able to use the view to figure out which video file to play, and then pass the file path into the template. All the files are hosted locally (for now, at least).
Is it possible to do with Django? And if so, how do I do it?
There are two ways you can do this -
Method 1: Pass parameter in URL and display video based on that parameter -
If you don't want to use models at any cost, use this, else try method 2.
Assuming you have saved all videos in your media directory and they all have unique names (serving as their ids).
your_app/urls.py -
from django.conf.urls import url
from . import views
urlpatterns = [
url(r'^video/(?P<vid>\w+)/$',views.display_video)
# \w will allow alphanumeric characters or string
]
Add this in the project's settings.py -
#Change this as per your liking
MEDIA_URL = '/media/'
MEDIA_ROOT = os.path.join(BASE_DIR, 'media')
your_app/views.py -
from django.conf import settings
from django.shortcuts import render
from django.http import HttpResponse
import os
import fnmatch
def display_video(request,vid=None):
if vid is None:
return HttpResponse("No Video")
#Finding the name of video file with extension, use this if you have different extension of the videos
video_name = ""
for fname in os.listdir(settings.MEDIA_ROOT):
if fnmatch.fnmatch(fname, vid+".*"): #using pattern to find the video file with given id and any extension
video_name = fname
break
'''
If you have all the videos of same extension e.g. mp4, then instead of above code, you can just use -
video_name = vid+".mp4"
'''
#getting full url -
video_url = settings.MEDIA_URL+video_name
return render(request, "video_template.html", {"url":video_url})
Then in your template file, video_template.html, display video as -
<video width="400" controls>
<source src="{{url}}" type="video/mp4">
Your browser does not support HTML5 video.
</video>
Note: There can be performance issue, iterating through all the files in the folder using os.listdir(). Instead, if possible, use a common file extension or use the next method (strongly recommended).
Method 2 : Storing video ids and correspondig file names in database -
Use same settings.py, urls.py and video_template.html as in method 1.
your_app/models.py -
from django.db import models
class videos(models.Model):
video_id = models.CharField(blank=False, max_length=32)
file_name = models.CharField(blank=False, max_length=500)
def __str__(self):
return self.id
your_app/views.py -
from django.conf import settings
from django.shortcuts import render, get_object_or_404
from django.http import HttpResponse
from .models import videos
def display_video(request,vid=None):
if vid is None:
return HttpResponse("No Video")
try:
video_object = get_object_or_404(videos, pk = vid)
except videos.DoesNotExist:
return HttpResponse("Id doesn't exists.")
file_name = video_object.file_name
#getting full url -
video_url = settings.MEDIA_URL+file_name
return render(request, "video_template.html", {"url":video_url})
So if you want to access any page with video id 97veqne0, just goto - localhost:8000/video/97veqne0

Django: Get relative uri from views.py

Here is what I have currently:
urls.py:
...
url(r'this/is/relative', 'myapp.views.callview', name='myapp_callview'),
...
views.py:
def callview(request, **kwargs):
# I can get the complete url by doing this
print request.build.absolute_uri() # Prints: https://domain:8080/myapp/this/is/relative
# How do I just get: /myapp/this/is/relative or even /this/is/relative
I would like to extract the relative uri from the view. I could just use regex, but I think there is already something out there that would let me do this.
This will give you "/myapp/this/is/relative":
from django.core import urlresolvers
relative_uri = urlresolvers.reverse("myapp_callview")
Link to Django docs page: https://docs.djangoproject.com/en/dev/ref/urlresolvers/

Issues with multiple languages

I want my app will be available in multiple languages (let say two,one is default english and one more).
And these both options available in my home page and there must be a link shown which makes user able to select his choice of language.
I am reading the Django official documentation for this
so any one can let me know the general idea how I can do that.
and one more thing......in settings.py there is default LANGUAGE_CODE = 'en-us' given,BUT as I want my app in more then one language so How i can specify that country code here.
like this works LANGUAGE_CODE = 'en-us','es-MX (Spanish)' or I have to do it in some way.
And what is the purpose of this .po extension in this.
settings.py
LANGUAGE_CODE='en_us'
gettext = lambda s: s
LANGUAGES = (
('en', gettext('English')),
('de', gettext('German')),
)
MIDDLEWARE_CLASSES = (
...
'lang.SessionBasedLocaleMiddleware',
)
lang.py
from django.conf import settings
from django.utils.cache import patch_vary_headers
from django.utils import translation
class SessionBasedLocaleMiddleware(object):
"""
This Middleware saves the desired content language in the user session.
The SessionMiddleware has to be activated.
"""
def process_request(self, request):
if request.method == 'GET' and 'lang' in request.GET:
language = request.GET['lang']
request.session['language'] = language
elif 'language' in request.session:
language = request.session['language']
else:
language = translation.get_language_from_request(request)
for lang in settings.LANGUAGES:
if lang[0] == language:
translation.activate(language)
request.LANGUAGE_CODE = translation.get_language()
def process_response(self, request, response):
patch_vary_headers(response, ('Accept-Language',))
if 'Content-Language' not in response:
response['Content-Language'] = translation.get_language()
translation.deactivate()
return response
Access different languages http://example.com/?lang=de
And finaly let django create your .po files. Heres the documentation for that.
You want internationalization (or localization) of your software. With C it is often done thru gettext (which is related to .po files). Probably django uses these things.