Embedding YouTube Videos from User Submitted Comments in Django Webpage - django

I am making a blog webpage in Django, and I have allowed comments for the blog posts. When a user submits a comment, I am able to use a function in my comment model to identify links they included in their text. I can also embed the video back in the template using an iframe and the list of urls the user had in the comment. The issue that I have is that I am wanting to show the embedded video in the exact same spot the user types in a link. For instance, if the user typed one paragraph of text, and then pasted in a YouTube link, and then they typed one more paragraph, I would want the video to be embedded in between the paragraphs. I have tried several things, but I just haven't been able to figure it out yet. I appreciate any answer I get. Here is the code for the comment model:
class Comment(models.Model):
post = models.ForeignKey('forum.Thread', related_name='comments')
username = models.CharField(max_length=20,default='guest')
text = models.TextField()
created_date = models.DateTimeField(default=timezone.now)
approved_comment = models.BooleanField(default=False)
def approve(self):
self.approved_comment = True
self.save()
def __str__(self):
return self.text
def get_youtube_urls(self):
urls = []
youtube_regex = (
r'(https?://)?(www\.)?'
'(youtube|youtu|youtube-nocookie)\.(com|be)/'
'(watch\?v=|embed/|v/|.+\?v=)?([^&=%\?]{11})')
matches = re.findall(youtube_regex, self.text)
for url in matches:
urls.append(url[5])
return urls
And here is the section of my template that deals with the comments:
<div class="comment">
<strong>Reply from user {{ comment.username }}</strong>
<p>{{ comment.text|linebreaks|urlize }}</p>
{% if comment.get_youtube_urls %}
{% for url in comment.get_youtube_urls %}
<div style="position:relative;height:0;padding-bottom:56.25%"><iframe src="https://www.youtube.com/embed/{{url}}?ecver=2" width="640" height="360" frameborder="0" style="position:absolute;width:50%;height:50%;left:0" allowfullscreen></iframe></div>
{% endfor %}
{% endif %}
<div class="date">{{ comment.created_date }}</div>
</div>
Does any one have any ideas on how to embed video where the user places the link in the text?

Previously, I explained such as below because I created an issue save regex permalink not work well.
So, if you want to test it you can try manually.
I check your regex patterns on http://www.pyregex.com with;
1. mode: findall
2. patterns:
(https?://)?(www\.)?(youtube|youtu|youtube-nocookie)\.(com|be)/(watch\?v=|embed/|v/|.+\?v=)?([^&=%\?]{11})
3. test strings:
https://youtu.be/yVpbFMhOAwE
https://www.youtube.com/watch?v=8Z5EjAmZS1o
https://www.youtube.com/embed/yVpbFMhOAwE
<iframe width="560" height="315" src="https://www.youtube.com/embed/Tlf00NT6mig" frameborder="0" allowfullscreen></iframe>
<iframe width="560" height="315" src="https://www.youtube.com/embed/KQzCxdO3hvo" frameborder="0" allowfullscreen></iframe>
https://www.youtube.com/watch?v=VslLZcV9ZcU&list=RD8Z5EjAmZS1o&index=2
4. result:
["https://","","youtu","be","","yVpbFMhOAwE"]
["https://","www.","youtube","com","watch?v=","8Z5EjAmZS1o"]
["https://","www.","youtube","com","embed/","yVpbFMhOAwE"]
["https://","www.","youtube","com","embed/","Tlf00NT6mig"]
["https://","www.","youtube","com","embed/","KQzCxdO3hvo"]
["https://","www.","youtube","com","watch?v=","VslLZcV9ZcU"]
I think you should do this to get the video id;
def get_youtube_urls(self):
youtube_regex = (
r'(https?://)?(www\.)?'
'(youtube|youtu|youtube-nocookie)\.(com|be)/'
'(watch\?v=|embed/|v/|.+\?v=)?([^&=%\?]{11})')
matches = re.findall(youtube_regex, self.text)
urls = []
for url in matches:
id = url[-1] # from last index of `matches` list.
if len(id) == 11: # check if `id` is valid or not.
urls.append(id)
return urls
If it doesn't work, I suggest you to change the value of youtube_regex above with this (without saving in the tuple).
youtube_regex = r'(https?://)?(www\.)?(youtube|youtu|youtube-nocookie)\.(com|be)/(watch\?v=|embed/|v/|.+\?v=)?([^&=%\?]{11})'
# OR, recomended to use this;
youtube_regex = r'http(?:s?):\/\/(?:www\.)?(youtube|youtu|youtube-nocookie)\.(com|be)/(watch\?v=|embed/|v/|.+\?v=)?([^&=%\?]{11})'
Update
To get the correct position, I suggest you to use finditer;
>>> youtube_regex = r'http(?:s?):\/\/(?:www\.)?(youtube|youtu|youtube-nocookie)\.(com|be)/(watch\?v=|embed/|v/|.+\?v=)?([^&=%\?]{11})'
>>> pattern = re.compile(youtube_regex)
>>> [{'origin': m.group(), 'id': m.group(4), 'position': m.start()} for m in pattern.finditer(test_strings) ]
[
{'position': 0, 'id': 'yVpbFMhOAwE', 'origin': 'https://youtu.be/yVpbFMhOAwE'},
{'position': 30, 'id': '8Z5EjAmZS1o', 'origin': 'https://www.youtube.com/watch?v=8Z5EjAmZS1o'},
{'position': 75, 'id': 'yVpbFMhOAwE', 'origin': 'https://www.youtube.com/embed/yVpbFMhOAwE'},
{'position': 156, 'id': 'Tlf00NT6mig', 'origin': 'https://www.youtube.com/embed/Tlf00NT6mig'},
{'position': 280, 'id': 'KQzCxdO3hvo', 'origin': 'https://www.youtube.com/embed/KQzCxdO3hvo'},
{'position': 366, 'id': 'VslLZcV9ZcU', 'origin': 'https://www.youtube.com/watch?v=VslLZcV9ZcU'}
]
>>
And then, handle it with templatetags. this script below isn't completed yet, you need to remove the tag such as <iframe, or else from the origin text comment..
import re
from django import template
from django.utils.safestring import mark_safe
register = template.Library()
def iframe_video(id):
"""
return html string iframe video from the `id`.
"""
return '<iframe src="https://www.youtube.com/embed/{}"></iframe>'.format(id)
#register.filter
def safe_comment(text_comment):
"""
{{ comment.text|safe_comment|linebreaks|urlize }}
"""
youtube_regex = (r'http(?:s?):\/\/(?:www\.)?'
'(youtube|youtu|youtube-nocookie)\.(com|be)/'
'(watch\?v=|embed/|v/|.+\?v=)?([^&=%\?]{11})')
pattern = re.compile(youtube_regex)
matches = [
{'origin': m.group(), 'id': m.group(4), 'position': m.start()}
for m in pattern.finditer(text_comment)
]
for match in matches:
id = match['id']
origin = match['origin']
position = match['position']
# text_comment.replace(origin, iframe_video(id))
# do something to replace tag <iframe, or else..
return mark_safe(text_comment)

Related

Newlines in textarea are doubled in number when saved

I am working on a Django wiki app. The user can enter markdown text in a textarea to either create or edit an entry. Whenever this happens though, the number of newlines between text are doubled. For example if the user entered 4 newlines in the textarea, the saved markdown file will have 8 newlines.
'''
# in views.py
class ContentForm(forms.Form):
content = forms.CharField(
required=True,
widget=forms.Textarea,
label="Contents")
def edit(request, title):
if request.method == 'POST':
# save_entry saves the markdown text to the given title.md
save_entry(title, request.POST['content'])
# redirect the user to the updated wiki page
return HttpResponseRedirect(reverse('entry', args=(title,)))
else:
# get_entry returns markdown text for a title
content = get_entry(title)
form = ContentForm(request.POST or None, initial={'content': content})
return render(request, "encyclopedia/edit.html", {
"title": title,
"content": content,
"form": form
})
# in edit.html
<h1>Edit {{ title }}</h1>
<form action="{% url 'edit' title %}" method="post">
{% csrf_token %}
{{ form }}
<input type="submit" value="Save Changes">
</form>
# save_entry and get_entry definitions
def save_entry(title, content):
"""
Saves an encyclopedia entry, given its title and Markdown
content. If an existing entry with the same title already exists,
it is replaced.
"""
filename = f"entries/{title}.md"
if default_storage.exists(filename):
default_storage.delete(filename)
default_storage.save(filename, ContentFile(content))
def get_entry(title):
"""
Retrieves an encyclopedia entry by its title. If no such
entry exists, the function returns None.
"""
try:
f = default_storage.open(f"entries/{title}.md")
return f.read().decode("utf-8")
except FileNotFoundError:
return None
'''
In this case I used a textarea widget, but before this I had just used the textarea html tag and that was not working either. To create a new page as well, I am not using a widget and that is doing the same thing too. I've been trying to fix this for many hours. What could be going wrong?
I fixed this by using bytes(content, 'utf8') before passing it to the save function.
See: Why did bytes() solve my newlines problem?
if request.method == 'POST':
save_entry(title, bytes(request.POST['new_content'], 'utf8'))
return HttpResponseRedirect(reverse('encyclopedia:entry', args=(title,)))

date-format not working on html page with datepicker

First, I'm relatively new to Django. I've seen my question addressed here with answers that I've tried to implement without success. Using a date picker that formats the date differently then how its stored and returned on the form initially.
forms.py
....
start_date = forms.Datefield(widget=forms.DateInput(format='%m/%d/%Y'), input_formats=['%m/%d/%Y'])
queries.html
....
<div class="col-4">
<label for="start_date" style="font-weight: bold;">Start Date</label>
<div class="input-group date" data-provide="datepicker">
<input type="text" name="start_date" id="start_date" value="{{queryform.start_date.value}}" class="form-control">
<div class="input-group-addon"><span class="glyphicon glyphicon-th"></span></div>
</div>
</div>
....
<form method="post">
{% csrf_token %}
<script>
$(function() {
$( ".datepicker" ).datepicker({
changeMonth: true,
dateFormat: 'mm/dd/yyyy',
changeYear: true,
yearRange: "2010:2025"
});
});
</script>
url.py
path('editqueries/<id>', views.editqueries, name='editqueries'),
views.py
def editqueries(request, id):
query_data = get_object_or_404(Query, pk=id)
if request.method == "POST":
query_form = QueryForm(request.POST, instance=query_data)
if query_form.is_valid():
the_query_name = query_form.cleaned_data["query_name"]
# get the hidden field from the html page (but not on the
# Django form)
current_query_name = request.POST["currentqueryname"]
# test to be sure if the names are not the same that changing
# the name doesn't create a duplicate query_name
if not the_query_name == current_query_name:
try:
test_query =
Query.objects.get(query_name=the_query_name)
except Query.DoesNotExist:
# this is allowed. Named changed does not create a
# duplicate
query_form.save()
query = Query.objects.all()
query_flag = "None"
context = {'queries': query, 'query_flag': query_flag}
return render(request, 'seakerUI/queries.html',
context)
# successful query means this name is in use.
# Stop the renaming of the query.
return HttpResponse("ERROR: Query Name '" +
the_query_name + "' Already exist!")
query_form.save()
query = Query.objects.all()
query_flag = "None"
context = {'queries': query, 'query_flag': query_flag}
return render(request, 'seakerUI/queries.html', context)
else:
return HttpResponse("Form is invalid.. errors:" +
str(query_form.errors))
else:
query_form = QueryForm(instance=query_data)
# tell the user the query is ready to be updated.
query_flag = "Edit"
context = {'queryform': query_form, 'query_flag': query_flag}
return render(request, 'seakerUI/queries.html', context)
queries.html
see code above
So when attempting to edit a query, the page is formatted with the date like "Aug. 2, 2019". However, if one submits the form without changing the date, the form is invalid and the form.error is date is invalid.
I've set the following line in settings.py
DATE_INPUT_FORMATS = ['%m/%d/$Y']
I've had 2 other formats in this definition but none seem to work.
I also executed
python manage.py diffsettings
and though it shows in the output the impact is negligible.
I've attempted using many examples of structuring the forms.py file using a widget function and without it without success. The problem does not appear to be with the javascript on the hmtl page.
NOTE: If I change the date when the edit query page presents it then the form validates. However, if one doesn't change the date and the form is submitted it is not valid and an error occurs. I shouldn't have to change the date to get the form to validate.
Suggestions?
You can try with html5 and WTF forms.
Html5 and WTFforms together can be used to select and date/month/year and process.
In form.py:
from wtforms.fields.html5 import DateField
Accept the inputs as shown :
dob= DateField('Password - Your Date of Birth', validators=[DataRequired()], format='%Y-%m-%d')
In html
form.dob(class="form-control form-control-lg")

How to upload images (like from my computer) to my django-tinymce formField?

I have an HTML page (not django admin) showing a WYSIYYG tinymce field:
What i need to do with it is writing some text (it works), upload some images to illustrate the text (it doesn't work) and finally if possible give a class to these uploaded images.
This is for a kind of 'page' generator, every content written in the edidor will show up as new page on my webite.
the form :
class PageForm(forms.Form):
name = forms.CharField(max_length=255)
content = forms.CharField(widget=TinyMCE())
the model:
class Page(models.Model):
name = models.CharField(max_length=255,
null=False,
blank=False,
unique=True,)
content = models.TextField(null=False,
blank=False)
slug = models.CharField(max_length=255)
def save(self, *args, **kwargs):
self.slug = slugify(self.name)
super(Page, self).save(*args, **kwargs)
the html page (basic):
<body>
{% if error %}
<p>Une erreur est survenue</p>
{% endif %}
{% if action == "update-page" %}
<form method="post" action="{% url "page_update" page.slug %}">
{% elif action == "create-page" %}
<form method="post" action="{% url 'page_create' %}">
{% endif %}
{% csrf_token %}
{{ form.as_p }}
<input type="submit" value="Enregistrer" />
</form>
</body>
For the moment when i click on the insert/edit icon it just offers me to give a 'link' and not upload an image.
So how do i have to setup my django and/or setup tinymce
Thank you.
(please consider in your answers that my english and my dev lvl is sometimes too weak to understand some parts of technical documentation)
The solution suggested by this tutorial works, and it uses a file picker JavaScript callback, but it overrides your TINYMCE_DEFAULT_CONFIG if you have one in settings.py.
So, I suggest adding that file_picker_callback in settings.py, like this:
TINYMCE_DEFAULT_CONFIG = {
"entity_encoding": "raw",
"menubar": "file edit view insert format tools table help",
"plugins": 'print preview paste importcss searchreplace autolink autosave save code visualblocks visualchars fullscreen image link media template codesample table charmap hr pagebreak nonbreaking anchor toc insertdatetime advlist lists wordcount imagetools textpattern noneditable help charmap emoticons quickbars',
"toolbar": "fullscreen preview | undo redo | bold italic forecolor backcolor | formatselect | image link | "
"alignleft aligncenter alignright alignjustify | outdent indent | numlist bullist checklist | fontsizeselect "
"emoticons | ",
"custom_undo_redo_levels": 50,
"quickbars_insert_toolbar": False,
"file_picker_callback": """function (cb, value, meta) {
var input = document.createElement("input");
input.setAttribute("type", "file");
if (meta.filetype == "image") {
input.setAttribute("accept", "image/*");
}
if (meta.filetype == "media") {
input.setAttribute("accept", "video/*");
}
input.onchange = function () {
var file = this.files[0];
var reader = new FileReader();
reader.onload = function () {
var id = "blobid" + (new Date()).getTime();
var blobCache = tinymce.activeEditor.editorUpload.blobCache;
var base64 = reader.result.split(",")[1];
var blobInfo = blobCache.create(id, file, base64);
blobCache.add(blobInfo);
cb(blobInfo.blobUri(), { title: file.name });
};
reader.readAsDataURL(file);
};
input.click();
}""",
"content_style": "body { font-family:Roboto,Helvetica,Arial,sans-serif; font-size:14px }",
}
I made this using https://django-filebrowser.readthedocs.io/en/3.5.2/quickstart.html
and https://django-tinymce.readthedocs.io/en/latest/usage.html.
settings.py
wyswyg = [ # what you see is what you get --- enables to add a text editor on website
"tinymce",
"grappelli",
"filebrowser",
]
INSTALLED_APPS = (
[
"django.contrib.admin",
"django.contrib.auth",
"django.contrib.contenttypes",
"django.contrib.sessions",
"django.contrib.messages",
"django.contrib.staticfiles",
]
+ wyswyg
)
TINYMCE_JS_ROOT = os.path.join(STATIC_ROOT, "tinymce")
urls.py
from filebrowser.sites import site
urlpatterns = [
url(r"^admin/filebrowser/", site.urls),
url(r"^tinymce/", include("tinymce.urls")),
path("admin/", admin.site.urls),
]
form
from tinymce.widgets import TinyMCE
class MailForm(forms.Form):
to = forms.SelectMultiple()
subject = forms.CharField(max_length=50)
content = forms.CharField(widget=TinyMCE(attrs={"cols": 80, "rows": 30}))
You can dynamically create pages with WYSWYG editors like django-tinymce.
First of all install django-tinymce pip install django-tinymce and add tinymce to INSTALLED_APPS in settings.py for your project
and add to urlpatterns
urlpatterns = [
path('admin/', admin.site.urls),
path('tinymce/', include('tinymce.urls')),
...
)
Create a Model just like you did
from tinymce.models import HTMLField
class Page(models.Model):
name = models.CharField(max_length=255,unique=True)
slug = models.CharField(max_length=255,unique=True)
content = models.HTMLField()
def save(self, *args, **kwargs):
self.slug = slugify(self.name)
super(Page, self).save(*args, **kwargs)
note that you didnt need to use null=False,blank=False to any field. It is set by default and you should use HTMLField() instead of TextField.
And your forms.py should be like
from django import forms
from django.forms.widgets import TextInput
from app.models import Page
from tinymce.widgets import TinyMCE
class PageForm(forms.ModelForm):
class Meta:
model = Page
exclude = ['slug']
widgets = {
'name' : TextInput(attrs={'class': 'form-control', 'placeholder': 'Page Title'}),
'content' : TinyMCE(attrs={'cols': 80, 'rows': 30,'class': 'form-control'}),
}
And template should be like
<form method="post" action="
{% if action == "update-page" %}
{% url "page_update" page.slug %}
{% elif action == "create-page" %}
{% url 'page_create' %}
{% endif %}
">
{% csrf_token %}
{{ form.as_p }}
<input type="submit" value="Enregistrer" />
</form>
This is the basic setup of django-tinymce. Additionally, the following options can be defined for tinymce in your Django project’s settings.py file.
DEFAULT = {
'selector': 'textarea',
'theme': 'modern',
'plugins': 'link image preview codesample contextmenu table code',
'toolbar1': 'bold italic underline | alignleft aligncenter alignright alignjustify '
'| bullist numlist | outdent indent | table | link image | codesample | preview code',
'contextmenu': 'formats | link image',
'menubar': False,
'inline': False,
'statusbar': True,
'height': 360,
}
For image handling, I'd suggest you to use the popular Pillow library and a models.ImageField.
This field only saves the URL / path of the image and not the actual image within the database. However, django saves the actual image in your static files assets folder.
The image will be served by your server when you put something like into a template containing the image object as a context variable.
A great tutorial is here: https://coderwall.com/p/bz0sng/simple-django-image-upload-to-model-imagefield
finally i found exactly what I was looking for here:
https://karansthr.gitlab.io/fosstack/how-to-set-up-tinymce-in-django-app/index.html
So if you what to setup a WYSIWYG editor in your django project, be able to upload images/files from the client computer and use it in admin and/or in your personalised forms just follow the steps, if it doesn't work perfectly you may need to check the code he uploaded on github witch is a bit different but functional with the very lasts versions of django (2.1 if you read this in 2054).

How to get rtf displayed in Django app?

I'm creating an blog app in Django. In a form I'm using ckeditor in order to get the rich text format from the user. Now the data is saved in the database in the RTF with all tags. Now I want to retrieve data from database and display it to the user but unable to do so. In output data with tags is displayed.
my code goes like this.
class blog(models.Model):
title = models.CharField(max_length = 200, unique = True)
slug = models.SlugField(max_length = 200, unique = True)
body = RichTextField()
uid = models.AutoField(primary_key = True)
posted_on = models.DateField(auto_now_add= True, db_index = True)
blogger = models.ForeignKey(blogger)
def __str__(self):
return self.title
form.html:- form for entry
<form method = 'POST' action = "{% url 'blog.views.addblog' %}">
{% csrf_token %}
<div class="form-group">
{}
{{form|crispy}}
<center>
<input type="submit" class="btn btn-success" value = "Post"></center>
</form>.
This is how I'm trying to display data on another HTML page. Blog is context passed containing blog details like body and title:
<h1>{{blog.title }}</h1>
<p> {{blog.body|linebreaks}}</p>
how to get data back in rich text format?
well a lot of research. Answer is so easy i was wondering why i couldn't figure it out.
I just had to use safe keyword in following manner.
<h1>{{blog.title }}</h1>
<p> {{blog.body|safe}}</p>

Merging querysets from different models

I have 2 models in one app and 1 view. I'm currently pulling information from 1 model perfectly fine. However i wish to pull in another model from the same app and output them both to the same page.
The idea of the page is it being a a news hub so it's pulling through different types of news posts (from one model) and a different type of post which is from the other model.
I'm fairly new to Django so go easy! :) Anyway here is the code:
//VIEWS
def news_home(request):
page_context = details(request, path="news-hub", only_context=True)
recent_posts = NewsPost.objects.filter(live=True, case_study=False).order_by("-posted")[:5]
recent_posts_pages = Paginator(recent_posts, 100)
current_page = request.GET.get("page", 1)
this_page = recent_posts_pages.page(current_page)
notes = BriefingNote.objects.filter(live=True).order_by("-posted")
news_categories = NewsCategory.objects.all()
news_context = {
"recent_posts": this_page.object_list,
"news_categories": news_categories,
"pages": recent_posts_pages,
"note": notes,
}
context = dict(page_context)
context.update(news_context)
return render_to_response('news_hub_REDESIGN.html', context, context_instance=RequestContext(request))
//model 1
class BriefingNote(models.Model):
title = models.CharField(max_length=300)
thumbnail = models.ImageField(upload_to='images/briefing_notes', blank=True)
file = models.FileField(upload_to='files/briefing_notes')
live = models.BooleanField(help_text="The post will only show on the frontend if the 'live' box is checked")
categories = models.ManyToManyField("NewsCategory")
# Dates
posted = models.DateTimeField(auto_now_add=True)
updated = models.DateTimeField(auto_now=True)
def __unicode__(self):
return u"%s" % self.title
// model 2
class NewsPost(models.Model):
title = models.CharField(max_length=400)
slug = models.SlugField(help_text="This will form the URL of the post")
summary = models.TextField(help_text="To be used on the listings pages. Any formatting here will be ignored on the listings page.")
post = models.TextField(blank=True)
#TO BE REMOVED????
thumbnail = models.ImageField(help_text="To be displayed on listings pages", upload_to="images/news", blank=True)
remove_thumbnail = models.BooleanField()
I'm outputting the content on the front end like so:
{% for post in recent_posts %}
<div class='news_first'>
<img class="news_thumb" src="/media/{% if post.article_type %}{{post.article_type.image}}{% endif %}{% if post.news_type %}{{post.news_type.image}}{% endif%}" alt="">
<h3><a href='{{post.get_absolute_url}}'>{% if post.article_type.title %}{{post.title}}{% endif %} <span>{{post.posted|date:"d/m/y"}}</span></a></h3>
<p class='news_summary'>
{% if post.thumbnail %}<a href='{{post.get_absolute_url}}'><img src='{% thumbnail post.thumbnail 120x100 crop upscale %}' alt='{{post.title}}' class='news_thumbnail'/></a>{% endif %}{{post.summary|striptags}} <a href='{{post.get_absolute_url}}'>Read full story »</a>
</p>
<div class='clearboth'></div>
</div>
{% endfor %}
I was thinking perhaps i could output them both within the same forloop however they need to ordered by -posted. So i though this could mess things up.
If you need anymore info please let me know.
Thanks in advance.
I've now solved the problem.
news_hub = list(chain(notes, recent_posts))
news_hub = sorted(
chain(notes, recent_posts),
key = attrgetter('posted'), reverse=True)[:10]