Wagtail - Override internal links in richtexteditor - django

Since I use wagtail headlessly the internal links get messed up due to them using the site-url listed in settings. Instead, I want to be able to override that same URL and point them to my frontend.
This post talks a little bit about it in 2018, but I'm hoping this has changed?
Wagtail: Is it possible to disable prepending internal link's with the site domain?
For external links you'd do something like this to override it:
class NewWindowExternalLinkHandler(LinkHandler):
# This specifies to do this override for external links only.
identifier = 'external'
#classmethod
def expand_db_attributes(cls, attrs):
href = attrs["href"]
print(attrs)
# Let's add the target attr, and also rel="noopener" + noreferrer fallback.
# See https://github.com/whatwg/html/issues/4078.
return '<a href="%s" target="_blank" rel="noopener noreferrer">' % escape(href)
Is it possible to do the same for internal links?
E.g now since I use a multi-site setup my link looks something like:
https://localhost.my-backend-api-domain.com/page/pagename
I want it to look like this:
https://my-frontend-domain.com/page/pagename

Also I was struggling to change the behaviour of the internal page links.
Intuitivly one would think to follow the external-link example and seethe identifier to external.
That's however not the case. After digging further into the source I was able to change the behaviour of the external link like this:
from django.utils.html import escape
from wagtail import hooks
from wagtail.rich_text.pages import PageLinkHandler
from wagtail.models import Page
class TargetNewInternalLinkHandler(PageLinkHandler):
#classmethod
def expand_db_attributes(cls, attrs):
try:
page = cls.get_instance(attrs)
return '<a href="%s" class="uk-link uk-link-text">' % escape(page.localized.specific.url)
except Page.DoesNotExist:
return "<a>"
#hooks.register('register_rich_text_features')
def register_external_link(features):
features.register_link_type(TargetNewInternalLinkHandler)
I know this doesn't exactly answer your question. But does it help you figure out a solution?

Related

Best practice of Flask route for app.route("/index") or ("/index.html")

Is there a convention to define app routes in Flask to add suffix ".html"? e.g,
#app.route("/index", methods=['GET'])
def index_func(){
return render_template("index.html")
}
#app.route("/index.html", methods=['GET'])
def index_func(){
return render_template("index.html")
}
Which would be the best practice? Thanks.
The best practice in this case is to use '/' for index and avoid using '/index' and '/index.html' altogether. An index page is another name for a home page which is a synonym for the root page of a site which is '/'. All other routes are necessarily prefixed with it so '/index', '/home', etc are redundant.
As for adding file extensions to routes, this is not only unnecessary but could be miss leading in the future if you want to serve different content types from that route using content-negotiation. For example, what if you wanted to serve a JSON version of the same page for mobile and SPA clients? I'm not aware of any sources that state omitting the file extension is a best practice but it's implicit in that every route example in Flask's documentation omits a file extension. For example, the Rendering Templates example, which is serving an HTML page, does not include a .html suffix in the route.
No. 1 The way you defined a function in python is wrong.
def func(){
}
wont work. Instead you would define a function like this:
def func():
print("Hi")
Then, coming to the route declaration you would use
#app.route("/index")
def index_func():
return render_template("index.html")
Also note when you just want to recieve GET methods you dont have to specify methods=['GET']

Django syndication framework: prevent appending SITE_ID to the links

According to the documentation here: https://djangobook.com/syndication-feed-framework/
If link doesn’t return the domain, the syndication framework will
insert the domain of the current site, according to your SITE_ID
setting
However, I'm trying to generate a feed of magnet: links. The framework doesn't recognize this and attempts to append the SITE_ID, such that the links end up like this (on localhost):
<link>http://localhost:8000magnet:?xt=...</link>
Is there a way to bypass this?
Here's a way to do it with monkey patching, much cleaner.
I like to create a separate folder "django_patches" for these kinds of things:
myproject/django_patches/__init__.py
from django.contrib.syndication import views
from django.contrib.syndication.views import add_domain
def add_domain_if_we_should(domain, url, secure=False):
if url.startswith('magnet:'):
return url
else:
return add_domain(domain, url, secure=False)
views.add_domain = add_domain_if_we_should
Next, add it to your INSTALLED_APPS so that you can patch the function.
settings.py
INSTALLED_APPS = [
'django_overrides',
...
]
This is a bit gnarly, but here's a potential solution if you don't want to give up on the Django framework:
The problem is that the method add_domain is buried deep in a huge method within syndication framework, and I don't see a clean way to override it. Since this method is used for both the feed URL and the feed items, a monkey patch of add_domain would need to consider this.
Django source:
https://github.com/django/django/blob/master/django/contrib/syndication/views.py#L178
Steps:
1: Subclass the Feed class you're using and do a copy-paste override of the huge method get_feed
2: Modify the line:
link = add_domain(
current_site.domain,
self._get_dynamic_attr('item_link', item),
request.is_secure(),
)
To something like:
link = self._get_dynamic_attr('item_link', item)
I did end up digging through the syndication source code and finding no easy way to override it and did some hacky monkey patching. (Unfortunately I did it before I saw the answers posted here, all of which I assume will work about as well as this one)
Here's how I did it:
def item_link(self, item):
# adding http:// means the internal get_feed won't modify it
return "http://"+item.magnet_link
def get_feed(self, obj, request):
# hacky way to bypass the domain handling
feed = super().get_feed(obj, request)
for item in feed.items:
# strip that http:// we added above
item['link'] = item['link'][7:]
return feed
For future readers, this was as of Django 2.0.1. Hopefully in a future patch they allow support for protocols like magnet.

Using django haystack search with global search bar in template

I have a django project that needs to search 2 different models and one of the models has 3 types that I need to filter based on. I have haystack installed and working in a basic sense (using the default url conf and SearchView for my model and the template from the getting started documentation is returning results fine).
The problem is that I'm only able to get results by using the search form in the basic search.html template and I'm trying to make a global search bar work with haystack but I can't seem to get it right and I'm not having a lot of luck with the haystack documentation. I found another question on here that led me to the following method in my search app.
my urls.py directs "/search" to this view in my search.views:
def search_posts(request):
post_type = str(request.GET.get('type')).lower()
sqs = SearchQuerySet().all().filter(type=post_type)
view = search_view_factory(
view_class=SearchView,
template='search/search.html',
searchqueryset=sqs,
form_class=HighlightedSearchForm
)
return view(request)
The url string that comes in looks something like:
http://example.com/search/?q=test&type=blog
This will get the query string from my global search bar but returns no results, however if I remove the .filter(type=post_type) part from the sqs line I will get search results again (albeit not filtered by post type). Any ideas? I think I'm missing something fairly obvious but I can't seem to figure this out.
Thanks,
-Sean
EDIT:
It turns out that I am just an idiot. The reason why my filtering on the SQS by type was returning no results was because I didn't have the type field included in my PostIndex class. I changed my PostIndex to:
class PostIndex(indexes.SearchIndex, indexes.Indexable):
...
type = indexes.CharField(model_attr='type')
and rebuilt and it all works now.
Thanks for the response though!
def search_posts(request):
post_type = str(request.GET.get('type')).lower()
sqs = SearchQuerySet().filter(type=post_type)
clean_query = sqs.query.clean(post_type)
result = sqs.filter(content=clean_query)
view = search_view_factory(
view_class=SearchView,
template='search/search.html',
searchqueryset=result,
form_class=HighlightedSearchForm
)
return view(request)

Django: how do I include a url link in a form label

My use case looks very basic but I couldn't find anything on the web!
The idea is a form with checkbox "I have read and agree to the terms and conditions"
And a link on "terms and conditions" which points to a page with such terms and conditions...
Classic!
So I have a field in my form as follows:
tos = forms.BooleanField(widget=forms.CheckboxInput(),
label=_(u'I have read and agree to the terms and conditions' % reverse('terms_of_use')),
initial=False)
where 'terms of use' is the name of one of my url patterns in urls.py
But I get an error:
ImproperlyConfigured: The included urlconf urls doesn't have any patterns in it
My urlconf works fine on the whole site so I supposed that the problem was that the urlconf is not yet populated when the form is rendered ?
I tried using lazy_reverse = lazy(reverse, str) instead of reverse, but it doesn't solve anything.
Is there a way to make this work ? The use case seems very very basic so there surely is a way to do it without having to break up the form inside my template ?!
lazy_reverse won't work since you're turning around and unlazying it the second after with your "...%s..." % lazy(blah) notation.
I suppose you could try to lazy the whole thing, i.e.
label = lazy(lambda: _("bla %s bla" % reverse('something')))
but I did not test this
alternatively, just override the label at __init__, i.e.
self.fields['myfield'].label = 'blah %s bla' % reverse('bla')
I use this syntax
from django.urls import reverse
from django.utils.functional import lazy
privacy = forms.BooleanField(label = lazy(lambda: _("Privacy <a href='%s' a>policy</a>" % reverse('privacy'))))
You can provide a link a form label like this:
foo_filter=forms.ModelChoiceField(FooFilter.objects.all(),
label=format_html('{}', reverse_lazy('foo-filter'),
FooFilter._meta.verbose_name))
See AppRegistryNotReady: lazy format_html()?

Django: creating/modifying the request object

I'm trying to build an URL-alias app which allows the user create aliases for existing url in his website.
I'm trying to do this via middleware, where the request.META['PATH_INFO'] is checked against database records of aliases:
try:
src: request.META['PATH_INFO']
alias = Alias.objects.get(src=src)
view = get_view_for_this_path(request)
return view(request)
except Alias.DoesNotExist:
pass
return None
However, for this to work correctly it is of life-importance that (at least) the PATH_INFO is changed to the destination path.
Now there are some snippets which allow the developer to create testing request objects (http://djangosnippets.org/snippets/963/, http://djangosnippets.org/snippets/2231/), but these state that they are intended for testing purposes.
Of course, it could be that these snippets are fit for usage in a live enviroment, but my knowledge about Django request processing is too undeveloped to assess this.
Instead of the approach you're taking, have you considered the Redirects app?
It won't invisibly alias the path /foo/ to return the view bar(), but it will redirect /foo/ to /bar/
(posted as answer because comments do not seem to support linebreaks or other markup)
Thank for the advice, I have the same feeling regarding modifying request attributes. There must be a reason that the Django manual states that they should be considered read only.
I came up with this middleware:
def process_request(self, request):
try:
obj = A.objects.get(src=request.path_info.rstrip('/')) #The alias record.
view, args, kwargs = resolve_to_func(obj.dst + '/') #Modified http://djangosnippets.org/snippets/2262/
request.path = request.path.replace(request.path_info, obj.dst)
request.path_info = obj.dst
request.META['PATH_INFO'] = obj.dst
request.META['ROUTED_FROM'] = obj.src
request.is_routed = True
return view(request, *args, **kwargs)
except A.DoesNotExist: #No alias for this path
request.is_routed = False
except TypeError: #View does not exist.
pass
return None
But, considering the objections against modifying the requests' attributes, wouldn't it be a better solution to just skip that part, and only add the is_routed and ROUTED_TO (instead of routed from) parts?
Code that relies on the original path could then use that key from META.
Doing this using URLConfs is not possible, because this aliasing is aimed at enabling the end-user to configure his own URLs, with the assumption that the end-user has no access to the codebase or does not know how to write his own URLConf.
Though it would be possible to write a function that converts a user-readable-editable file (XML for example) to valid Django urls, it feels that using database records allows a more dynamic generation of aliases (other objects defining their own aliases).
Sorry to necro-post, but I just found this thread while searching for answers. My solution seems simpler. Maybe a) I'm depending on newer django features or b) I'm missing a pitfall.
I encountered this because there is a bot named "Mediapartners-Google" which is asking for pages with url parameters still encoded as from a naive scrape (or double-encoded depending on how you look at it.) i.e. I have 404s in my log from it that look like:
1.2.3.4 - - [12/Nov/2012:21:23:11 -0800] "GET /article/my-slug-name%3Fpage%3D2 HTTP/1.1" 1209 404 "-" "Mediapartners-Google
Normally I'd just ignore a broken bot, but this one I want to appease because it ought to better target our ads (It's google adsense's bot) resulting in better revenue - if it can see our content. Rumor is it doesn't follow redirects so I wanted to find a solution similar to the original Q. I do not want regular clients accessing pages by these broken urls, so I detect the user-agent. Other applications probably won't do that.
I agree a redirect would normally be the right answer.
My (complete?) solution:
from django.http import QueryDict
from django.core.urlresolvers import NoReverseMatch, resolve
class MediapartnersPatch(object):
def process_request(self, request):
# short-circuit asap
if request.META['HTTP_USER_AGENT'] != 'Mediapartners-Google':
return None
idx = request.path.find('?')
if idx == -1:
return None
oldpath = request.path
newpath = oldpath[0:idx]
try:
url = resolve(newpath)
except NoReverseMatch:
return None
request.path = newpath
request.GET = QueryDict(oldpath[idx+1:])
response = url.func(request, *url.args, **url.kwargs)
response['Link'] = '<%s>; rel="canonical"' % (oldpath,)
return response