Creating Draftail entity with additional data - django

I've been using Wagtail to create a website with additional text annotations. The user flow is that there is some highlighted text in a paragraph, which when clicked shows an annotation off to one side. The expected HTML result is:
A sentence with <span class='link'>A link<span class='hidden-text'>Hidden text</span></span>
I would like to achieve this with a single item on the draftail menu, with a UI similar to the URL creator- the user selects the text, and adds the annotation text.
I have followed the instructions on https://docs.wagtail.io/en/stable/advanced_topics/customisation/extending_draftail.html to create a new inline style which produces the link, however I can't then add the hidden-text:
# 1. Use the register_rich_text_features hook.
#hooks.register('register_rich_text_features')
def register_mark_feature(features):
"""
Registering the `mark` feature, which uses the `MARK` Draft.js inline style type,
and is stored as HTML with a `<mark>` tag.
"""
feature_name = 'mark'
type_ = 'SAMPLE'
tag = 'sample'
# 2. Configure how Draftail handles the feature in its toolbar.
control = {
'type': type_,
'label': '?',
'description': 'Hint link',
}
# 3. Call register_editor_plugin to register the configuration for Draftail.
features.register_editor_plugin(
'draftail', feature_name, draftail_features.InlineStyleFeature(control)
)
# 4.configure the content transform from the DB to the editor and back.
db_conversion = {
'from_database_format': {tag: InlineStyleElementHandler(type_)},
'to_database_format': {'style_map': {type_: tag}},
}
# 5. Call register_converter_rule to register the content transformation conversion.
features.register_converter_rule('contentstate', feature_name, db_conversion)
# 6. (optional) Add the feature to the default features list to make it available
# on rich text fields that do not specify an explicit 'features' list
features.default_features.append('mark')

to get What you want, easiest way is to create your own Template tags filters, create your own markdown replacements, let's say, in the rich text you assigned the link to part of your paragraph like this "Click here this is a hidden text" and then you put [ht] right before the "this is a hidden text" and a[th] right after that targeted hidden text,then in the template use self.field_name|replace:"[ht]"|replace:"[th]"
in template tag file (for example myapp/templatetags/my_richtext.py):
from django import template
from wagtail.images.models import Image
from django.utils.safestring import mark_safe
register = template.Library()
#register.filter(needs_autoescape=True)
def replace(value, arg, autoescape=True):
if arg == "[ht]": result = value.replace(arg, "<span class='hidden'>")
elif arg == "[th]": result = value.replace(arg, "</span>")
return mark_safe(result)

Related

Trying to write a custom template tag in Django that finds a phone number in text and converts it to a link

I want to convert this string tel:123-456-7890.1234 to a a link in html. The final output would be 123-456-7890 ext 1234
I'm not great with Regex and I'm REALLY close, but I need some help. I know that I'm not all the way there with the regex and output. How do I change what I have to make it work?
import re
#register.filter(name='phonify')
#stringfilter
def phonify(val):
"""
Pass the string 'tel:1234' to the filter and the right tel link is returned.
"""
# find every instance of 'tel' and then get the number after it
for tel in re.findall(r'tel:(\d{3}\D{0,3}\d{3}\D{0,3}\d{4})\D*(\d*)', val):
# format the tag for each instance of tel
tag = '{}'.format(tel, tel)
# replace the tel instance with the new formatted html
val = val.replace('tel:{}'.format(tel), tag)
# return the new output to the template context
return val
I added the wagtail tag as I've seen other solutions for this in Wagtail and is something needed for Wagtail, so this might be helpful to others.
You can use re.sub to perform a find and replace:
import re
#register.filter(name='phonify')
#stringfilter
def phonify(val):
tel_rgx = r'tel:(\d{3}\D{0,3}\d{3}\D{0,3}\d{4}\D*\d*)'
return re.sub(tel_rgx, r'\1', val)
Note however that in your template, you will need to mark the result as "safe", otherwise it will replace < with < etc and thus render <a href= as text.
You can mark the string as safe in your template fiter as well:
import re
from django.utils.safestring import mark_safe
#register.filter(name='phonify')
#stringfilter
def phonify(val):
tel_rgx = r'tel:(\d{3}\D{0,3}\d{3}\D{0,3}\d{4}\D*\d*)'
return mark_safe(re.sub(tel_rgx, r'\1', val))
Regardless how you do this, it will however mark all items as safe, even tags, etc. that are part of the original string, and thus should be escaped. Therefore, I'm not sure that this is a good idea.

How to enter a list in WTForms?

What I'm trying to do
I'm trying to enter a list of tags in flask that should become passable as a list but I can't figure out how to do it in flask, nor can I find documentation to add lists (of strings) in flask_wtf. Has anyone have experience with this?
Ideally I would like the tags to be selectively delete-able, after you entered them. So that you could enter.
The problem
Thus far my form is static. You enter stuff, hit submit, it gets processed into a .json. The tags list is the last element I can't figure out. I don't even know if flask can do this.
A little demo of how I envisioned the entry process:
How I envisioned the entry process:
The current tags are displayed and an entry field to add new ones.
[Tag1](x) | [Tag2](x)
Enter new Tag: [______] (add)
Hit (add)
[Tag1](x) | [Tag2](x)
Enter new Tag: [Tag3__] (add)
New Tag is added
[Tag1](x) | [Tag2](x) | [Tag3](x)
Enter new Tag: [______]
How I envisioned the deletion process:
Hitting the (x) on the side of the tag should kill it.
[Tag1](x) | [Tag2](x) | [Tag3](x)
Hit (x) on Tag2. Result:
[Tag1](x) | [Tag3](x)
The deletion is kind of icing on the cake and could probably be done, once I have a list I can edit, but getting there seems quite hard.
I'm at a loss here.
I basically want to know if it's possible to enter lists in general, since there does not seem to be documentation on the topic.
Your description is not really clear (is Tag1 the key in the JSON or is it Tag the key, and 1 the index?)
But I had a similar issue recently, where I wanted to submit a basic list in JSON and let WTForms handle it properly.
For instance, this:
{
"name": "John",
"tags": ["code", "python", "flask", "wtforms"]
}
So, I had to rewrite the way FieldList works because WTForms, for some reason, wants a list as "tags-1=XXX,tags-2=xxx".
from wtforms import FieldList
class JSONFieldList(FieldList):
def process(self, formdata, data=None):
self.entries = []
if data is None or not data:
try:
data = self.default()
except TypeError:
data = self.default
self.object_data = data
if formdata:
for (index, obj_data) in enumerate(formdata.getlist(self.name)):
self._add_entry(formdata, obj_data, index=index)
else:
for obj_data in data:
self._add_entry(formdata, obj_data)
while len(self.entries) < self.min_entries:
self._add_entry(formdata)
def _add_entry(self, formdata=None, data=None, index=None):
assert not self.max_entries or len(self.entries) < self.max_entries, \
'You cannot have more than max_entries entries in this FieldList'
if index is None:
index = self.last_index + 1
self.last_index = index
name = '%s-%d' % (self.short_name, index)
id = '%s-%d' % (self.id, index)
field = self.unbound_field.bind(form=None, name=name, id=id, prefix=self._prefix, _meta=self.meta,
translations=self._translations)
field.process(formdata, data)
self.entries.append(field)
return field
On Flask's end to handle the form:
from flask import request
from werkzeug.datastructures import ImmutableMultiDict
#app.route('/add', methods=['POST'])
def add():
form = MyForm(ImmutableMultiDict(request.get_json())
# process the form, form.tags.data is a list
And the form (notice the use of JSONFieldList):
class MonitorForm(BaseForm):
name = StringField(validators=[validators.DataRequired(), validators.Length(min=3, max=5)], filters=[lambda x: x or None])
tags = JSONFieldList(StringField(validators=[validators.DataRequired(), validators.Length(min=1, max=250)], filters=[lambda x: x or None]), validators=[Optional()])
I found a viable solution in this 2015 book, where a tagging system is being build for flask as part of a blog building exercise.
It's based on Flask_SQLAlchemy.
Entering lists therefore is possible with WTForms / Flask by submitting the items to the database via, e.g. FieldList and in the usecase of a tagging system, reading them from the database back to render them in the UI.
If however you don't want to deal with O'Rielly's paywall (I'm sorry, I can't post copyrighted material here) and all you want is a solution to add tags, check out taggle.js by Sean Coker. It's not flask, but javascript, but it does the job.

How to add superscript to Draftail in Wagtail 2.0

I have added superscript to the Draftail editor in Wagtail following these instructions Adding superscript to Draftail in Wagtail 2.0
It works and renders with the "sup" tag on front end, but does not display as superscript on backend. I've tried adding 'style': 'vertical-align: super' in the feature config, but this does not help. Any suggestions?
#hooks.register('register_rich_text_features')
def register_superscript_feature(features):
feature_name = 'superscript'
type_ = 'SUPERSCRIPT'
tag = 'sup'
# Configure how Draftail handles the feature in its toolbar.
control = {
'type': type_,
'label': 'x²',
'description': 'Superscript',
'style': 'vertical-align: super',
}
# Call register_editor_plugin to register the configuration for Draftail.
features.register_editor_plugin(
'draftail', feature_name, draftail_features.InlineStyleFeature(control)
)
# Configure the content transform from the DB to the editor and back.
db_conversion = {
'from_database_format': {tag: InlineStyleElementHandler(type_)},
'to_database_format': {'style_map': {type_: tag}},
}
# Call register_converter_rule to register the content transformation conversion.
features.register_converter_rule('contentstate', feature_name, db_conversion)
I would like for it to display as superscript in the editor as well.

web Crawling and Extracting data using scrapy

I am new to python as well as scrapy.
I am trying to crawl a seed url https://www.health.com/patients/status/.This seed url contains many urls. But I want to fetch only urls that contain Faci/Details/#somenumber from the seed url .The url will be like below:
https://www.health.com/patients/status/ ->https://www.health.com/Faci/Details/2
-> https://www.health.com/Faci/Details/3
-> https://www.health.com/Faci/Details/4
https://www.health.com/Faci/Details/2 -> https://www.health.com/provi/details/64
-> https://www.health.com/provi/details/65
https://www.health.com/Faci/Details/3 -> https://www.health.com/provi/details/70
-> https://www.health.com/provi/details/71
Inside each https://www.health.com/Faci/Details/2 page there is https://www.health.com/provi/details/64
https://www.health.com/provi/details/65 ... .Finally I want to fetch some datas from
https://www.health.com/provi/details/#somenumber url.How can I achieve the same?
As of now I have tried the below code from scrapy tutorial and able to crawl only url that contains https://www.health.com/Faci/Details/#somenumber .Its not going to https://www.health.com/provi/details/#somenumber .I tried to set depth limit in settings.py file.But it doesn't worked.
import scrapy
from scrapy.spiders import CrawlSpider, Rule
from scrapy.linkextractors import LinkExtractor
from news.items import NewsItem
class MySpider(CrawlSpider):
name = 'provdetails.com'
allowed_domains = ['health.com']
start_urls = ['https://www.health.com/patients/status/']
rules = (
Rule(LinkExtractor(allow=('/Faci/Details/\d+', )), follow=True),
Rule(LinkExtractor(allow=('/provi/details/\d+', )),callback='parse_item'),
)
def parse_item(self, response):
self.logger.info('Hi, this is an item page! %s', response.url)
item = NewsItem()
item['id'] = response.xpath("//title/text()").extract()
item['name'] = response.xpath("//title/text()").extract()
item['description'] = response.css('p.introduction::text').extract()
filename='details.txt'
with open(filename, 'wb') as f:
f.write(item)
self.log('Saved file %s' % filename)
return item
Please help me to proceed further?
To be honest, the regex-based and mighty Rule/LinkExtractor gave me often a hard time. For simple project it is maybe an approach to extract all links on page and then look on the href attribute. If the href matches your needs, yield a new Response object with it. For instance:
from scrapy.http import Request
from scrapy.selector import Selector
...
# follow links
for href in sel.xpath('//div[#class="contentLeft"]//div[#class="pageNavigation nobr"]//a').extract():
linktext = Selector(text=href).xpath('//a/text()').extract_first()
if linktext and linktext[0] == "Weiter":
link = Selector(text=href).xpath('//a/#href').extract()[0]
url = response.urljoin(link)
print url
yield Request(url, callback=self.parse)
Some remarks to your code:
response.xpath(...).extract()
This will return a list, maybe you want to have a look on extract_first() which provide the first item (or None).
with open(filename, 'wb') as f:
This will overwrite the file several times. You will only gain the last item saved. Also you open the file in binary mode ('b'). From the filename I guess you want to read it as text? Use 'a' to append? See open() docs
An alternative is to use the -o flag to use scrapys facilities to store the items to JSON or CSV.
return item
It is a good style to yield items instead of return them. At least if you need to create several items from one page you need to yield them.
Another good approach is: Use one parse() function for one type/kind of page.
For instance every page in start_urls fill end up in parse(). From that you extract could extract the links and yield Requests for each /Faci/Details/N page with a callback parse_faci_details(). In parse_faci_details() you extract again the links of interest, create Requests and pass them via callback= to e.g. parse_provi_details().
In this function you create the items you need.

What does these mean in SUMMERNOTE_CONFIG in django-summernote?

settings.py
SUMMERNOTE_CONFIG = {
'attachment_upload_to': my_custom_upload_to_func(),
'attachment_storage_class': 'my.custom.storage.class.name',
'attachment_model': 'my.custom.attachment.model',
}
as you know django-summernote is a rich text editor field used in django.
but when you implement that plugin to your app you get only minimum features and default options. when you add SUMMERNOTE_CONFIG you can customize your rich text field and add more features in it.
for example:
In your question you add some keys and values in 'SUMMERNOTE_CONFIG' it is about adding attachments. attachment_upload_to is path to the folder which needs to save attachments. attachment_model is to create a model for your attachments
eg:
# my/custom/attachment.py
from django_summernote import AbstractAttachment
class Attachment(django_summernote.AbstractAttachment):
pass
# django_project/settings.py
SUMMERNOTE_CONFIG = {
'attachment_upload_to': '/path/to/attachment_folder',
'attachment_model': 'my.custom.attachment.Attachment',
}