Django url path converter not working in production - django

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)

Related

Django: prefix/postfix language slug in i18n_urls

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

Testing Django Rest Framework: how to test hyperlink relations?

I'm trying to create a true unit test for a customized DjangoRestFramework Hyperlinked related field. But I cannot seem to get around this error:
django.core.exceptions.ImproperlyConfigured: Could not resolve URL for hyperlinked relationship using view name "relatedtestmodel-detail". You may have failed to include the related model in your API, or incorrectly configured the `lookup_field` attribute on this field.
And here is the unit test, stripped down to simplify the example:
from django.conf.urls import url
from django.test import TestCase, override_settings
from api_tests.models import APITestModel, RelatedTestModel
from api_tests.serializers import APITestModelSerializer
def dummy_view(request, pk):
pass
urlpatterns = [
url(r'/path/is/irrelevant/', dummy_view, name='relatedtestmodel-detail')
]
#override_settings(ROOT_URLCONF='tests.test_relations')
class HyperlinkedRelatedFieldTestCase(TestCase):
def setUp(self):
self.parent = APITestModel.objects.create()
self.child = RelatedTestModel.objects.create(parent=self.parent)
assert self.child.parent.id == self.parent.id
def test_to_internal_value_correct_error_message(self):
queryset = APITestModel.objects.all()
serializer = APITestModelSerializer(queryset, many=True, context={'request': None})
expected = [{'foo': 'bar'}]
self.assertEqual(serializer.data, expected)
I more or less lifted the test from https://github.com/encode/django-rest-framework/blob/master/tests/test_relations_hyperlink.py, because I figured who knows best how to unit test DRF than the makers of DRF? But as it stands, my test refuses to run. The error is raised during the test, when I attempt to access serializer.data for the assert.
Notice in particular that I override the settings with a custom urlpatterns (which is this same file, hence the urlpatterns at the top). So I don't understand why DRF thinks that url name doesn't exist - I can clearly see that my url conf in fact has ONLY that view name! I've even gone so far as to edit my actual URL conf and replace it with the single, named, dummy url pattern shown here, and removedthe settings override, just to be sure that it wasn't that the override_settings simply wasn't working, but even then I get the same error.
To my eye, the dummy url pattern is exactly the same as how DRF did it in their tests. Anyone have any ideas what is going on?
A bit more requested context:
api_tests.models:
from django.db import models
class APITestModel(models.Model):
pass
class RelatedTestModel(models.Model):
parent = models.ForeignKey(
APITestModel,
related_name='children',
related_query_name='child'
)
I do not have access to the traceback at this time, but I can confirm it did not pass through any of my code - it was all isolated to the DjangoRestFramework code, basically exclusively relations.py
Preamble
A few things this question is lacking
No definition of APITestModelSerializer
RelatedTestModel is not used in the test and therefore irrelevant for the example
No error stacktrace
No "useful" expected dict for the asserts
APITestModel has no fields so it can't be serialized (your test shouldn't even have run)
Minor things but still relevant
You are creating specific instances of APITestModel and RelatedTestModel in the setUp but in the test you serialize all instances of APITestModel
The line assert self.child.parent.id == self.parent.id should not be in the setUp. It should be in a separate test
My changes
I deleted all irrelevant information for this question mentioned above
I added an integer field to APITestModel
I changed the urlpatterns element from url(...) to path(...)
I added a regex to the relative path
The serializer is a subclass of HyperlinkedModelSerializer and includes fields "url" and "year"
My project and app urls.py files are the "stock" ones (not shown here) to emphasize that this test resolves the path in isolation.
Changed #override_settings(ROOT_URLCONF='tests.test_relations') to #override_settings(ROOT_URLCONF=__name__)
Code
models.py
from django.db import models
class APITestModel(models.Model):
year = models.IntegerField(null=False)
serializers.py
from rest_framework import serializers
from api.models import APITestModel
class APITestModelSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = APITestModel
fields = ["url", "year"]
test_serializers.py
from django.test import TestCase, override_settings
from django.urls import path
from api.models import APITestModel
from api.serializers import APITestModelSerializer
urlpatterns = [
path('whateveryouwant/<int:pk>/', lambda request: None, name='apitestmodel-detail'),
]
#override_settings(ROOT_URLCONF=__name__)
class HyperlinkedRelatedFieldTestCase(TestCase):
def setUp(self):
# Populate db with APITestModel instances
_ = APITestModel.objects.create(year=1960)
_ = APITestModel.objects.create(year=1961)
_ = APITestModel.objects.create(year=1962)
def test_to_internal_value_correct_error_message(self):
queryset = APITestModel.objects.all()
serializer = APITestModelSerializer(queryset, many=True, context={'request': None})
expected = [
{'url': '/whateveryouwant/1/', 'year': 1960},
{'url': '/whateveryouwant/2/', 'year': 1961},
{'url': '/whateveryouwant/3/', 'year': 1962},
]
self.assertEqual(serializer.data, expected)
The other files in the project are the default ones created automatically by django + djangorestframework.
For future readers, I created a github project with this working code and can be found here: https://github.com/Alechan/drf_test_hyperlink_relations

Loggedin pages after authintification

I am learning django by myself and created mini project for learning pupose and as example took a kidgarden.
I have a directorry sun and with two apps inside it accounts and equity apps.
In accounts i have templates with login signup htmls. This section works fine without any problem.
Your directory structure is still not clear. Do this. If it's works, please comment and I will explain what was wrong in your code.
in main dictionary (sun) urls.py, copy and paste this code.
from django.contrib import admin
from django.urls import path,include
from . import views
from accounts import views as account_views # added line
urlpatterns = [
path('admin/', admin.site.urls),
path('',views.HomePage.as_view(),name='home'),
path('accounts/',include('accounts.urls',namespace='accounts')),
path('accounts/',include('django.contrib.auth.urls')),
# path('kids/',views.TestPage.as_view(),name='kids'), # comment this url
path('kids/', account_views.home, name='kids'), # added url
path('thanks/',views.ThanksPage.as_view(),name='thanks'),
]
----- Edit ----
Initially, this was URL line. path('kids/',views.TestPage.as_view(),name='kids'). this was calling this function:
class ProductDetail(models.Model):
class TestPage(TemplateView):
template_name = 'equity/kids.html'
And your logic was written in this function (views in equity app):
def home(request):
stock = ChildForm()
if request.method == "POST":
stock = ChildForm(request.POST)
if stock.is_valid():
data = stock.save(commit=True)
name = data.name
context={
'name':name, }
else:
stock = ChildForm()
return render(request,'equity/kids.html',{'stock':stock})
return render(request,'equity/garden.html',context)
return render(request,'equity/kids.html',{'stock':stock)
You had to call right function which was in equity' views. So solution was this:
replace this path('kids/',views.TestPage.as_view(),name='kids')
with this:
path('kids/', account_views.home, name='kids')
importing views from equity is first thing.
from accounts import views as account_views

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/