To allow scrolling in Safari mobile, we set the content of text_view_comments frame in a jQuery UI layout.
So the automated scrolling operations in c_sync.js must be adjustable to the right part to scroll.
Also, if a comment have to be shown outside of the current viewport, we scroll the correct part to that viewport
and then set the comment top Y offset to juste what it needs to avoid the "Add comment" button after scrolling
operation.
If not in Safari mobile, we add an offset here to avoid comment to display under the "Add comment" button.
from cm.cm_settings import VALID_EMAIL_FOR_PUBLISH, SITE_NAME
from cm.converters import convert_from_mimetype
from cm.converters.pandoc_converters import pandoc_convert
from cm.models import Text, TextVersion, Comment, Attachment
from cm.utils.files import remove_extension
from cm.utils.mail import EmailMessage
from cm.views import get_text_by_keys_or_404
from django import forms
from cm.message import display_message
from django.conf import settings
from django.core.urlresolvers import reverse
from django.forms import ModelForm
from django.forms.util import ErrorList
from django.http import HttpResponse, HttpResponseRedirect
from django.shortcuts import render_to_response
from django.template import RequestContext
from django.template.loader import render_to_string
from django.utils.translation import ugettext as _, ugettext_lazy
from django.db import connection, transaction
from mimetypes import guess_type
from cm.activity import register_activity
from cm.security import has_global_perm
import os
from BeautifulSoup import BeautifulStoneSoup
import re
from base64 import b64decode
class CreateTextUploadForm(ModelForm):
file = forms.FileField(required=False,
label=ugettext_lazy("Upload file (optional)"),
help_text=ugettext_lazy("Upload a file from your computer instead of using the direct input above"),)
title = forms.CharField(required=False,
label=ugettext_lazy("Title"))
class Meta:
model = TextVersion
fields = ('title', 'format', 'tags') #, 'note'
def clean(self):
cleaned_data = self.cleaned_data
if not cleaned_data.get('file', None) :
msg = _("You should specify a file to upload.")
self._errors["file"] = ErrorList([msg])
return cleaned_data
class CreateTextImportForm(ModelForm):
file = forms.FileField(required=True,
label=ugettext_lazy("Upload XML file"),
help_text=ugettext_lazy("Upload a previously exported XML file from your computer"),)
class Meta:
model = TextVersion
fields = ()
def clean(self):
cleaned_data = self.cleaned_data
if not cleaned_data.get('file', None) :
msg = _("You should specify a file to upload.")
self._errors["file"] = ErrorList([msg])
return cleaned_data
uploaded_file = self.cleaned_data['file']
if (uploaded_file.content_type != 'text/xml' and (uploaded_file.content_type != 'application/octet-stream' or cleaned_data.get('mime', 'application/xml') != 'application/xml')):
msg = _("The imported file should be an XML file generated by co-ment when exporting a text and comments.")
self._errors["file"] = ErrorList([msg])
return cleaned_data
soup = BeautifulStoneSoup(uploaded_file)
if not soup.co_ment_text:
msg = _("No co_ment_text node found in XML.")
self._errors["file"] = ErrorList([msg])
return cleaned_data
for mandatory_child in ['title', 'created', 'modified', 'name', 'email', 'format', 'content']:
if not getattr(soup.co_ment_text, mandatory_child):
msg = _('No %(tag)s node found in XML.' %{"tag":mandatory_child})
self._errors["file"] = ErrorList([msg])
return cleaned_data
cleaned_data['soup'] = soup
return cleaned_data
class CreateTextContentForm(ModelForm):
title = forms.CharField(required=True,
label=ugettext_lazy("Title"),
help_text=ugettext_lazy("The title of your text"),
widget=forms.TextInput)
class Meta:
model = TextVersion
fields = ('title', 'format', 'content','tags') #, 'note'
@has_global_perm('can_create_text')
def text_create_content(request):
text, rep = _text_create_content(request, CreateTextContentForm)
return rep
def redirect_post_create(text) :
return HttpResponseRedirect(reverse('text-view', args=[text.key]))
def _text_create_content(request, createForm):
document = ""
if request.method == 'POST':
form = createForm(request.POST)
if form.is_valid():
text = create_text(request.user, form.cleaned_data)
register_activity(request, "text_created", text)
display_message(request, _(u'Text "%(text_title)s" has been created') %{"text_title":text.get_latest_version().title})
return text, redirect_post_create(text)
else:
form = createForm()
return None, render_to_response('site/text_create_content.html', {'document':document, 'form' : form}, context_instance=RequestContext(request))
def _text_create_upload(request, createForm):
if request.method == 'POST':
form = createForm(request.POST, request.FILES)
if form.is_valid():
# should convert?
if form.cleaned_data['file']:
try:
uploaded_file = form.cleaned_data['file']
content, attachs = convert_from_mimetype(uploaded_file.temporary_file_path(),
uploaded_file.content_type,
format=form.cleaned_data['format'],
)
form.cleaned_data['content'] = content
form.cleaned_data['attachs'] = attachs
# set title if not present
if not form.cleaned_data.get('title', None):
form.cleaned_data['title'] = remove_extension(uploaded_file.name)
del form.cleaned_data['file']
except:
raise
text = create_text(request.user, form.cleaned_data)
register_activity(request, "text_created", text)
display_message(request, _(u'Text "%(text_title)s" has been created')%{"text_title":text.get_latest_version().title})
return text, redirect_post_create(text)
else:
form = createForm()
return None, render_to_response('site/text_create_upload.html', {'form' : form}, context_instance=RequestContext(request))
def _text_create_import(request, createForm):
if request.method == 'POST':
form = createForm(request.POST, request.FILES)
if form.is_valid():
soup = form.cleaned_data['soup']
if not soup.co_ment_text:
raise Exception('Bad Soup')
# Process attachments first to create new keys.
attachments_keys_map = {}
if soup.co_ment_text.attachments:
for imported_attachement in soup.co_ment_text.attachments.findAll('attachment'):
# Creates attachment object.
filename = 'imported_attachment'
attachment = Attachment.objects.create_attachment(filename=filename, data=b64decode(imported_attachement.data.renderContents()), text_version=None)
# Stores key mapping.
attachments_keys_map[imported_attachement.key.renderContents()] = attachment.key
# Process text.
form.cleaned_data['title'] = soup.co_ment_text.title.renderContents()
form.cleaned_data['format'] = soup.co_ment_text.format.renderContents()
form.cleaned_data['content'] = re.sub(r'^<!\[CDATA\[|\]\]>$', '', soup.co_ment_text.content.renderContents())
form.cleaned_data['name'] = soup.co_ment_text.find('name').renderContents()
form.cleaned_data['email'] = soup.co_ment_text.email.renderContents()
if soup.co_ment_text.tags:
form.cleaned_data['tags'] = soup.co_ment_text.tags.renderContents()
# Replaces attachements keys in content.
for old_key in attachments_keys_map.keys():
form.cleaned_data['content'] = re.sub(old_key, attachments_keys_map[old_key], form.cleaned_data['content'])
form.cleaned_data['content'] = re.sub(r'src="/attach/', 'src="' + settings.SITE_URL + '/attach/', form.cleaned_data['content'])
# Creates text.
text = create_text(request.user, form.cleaned_data)
# Brute updates of dates (cannot do it through django models since fields are set with auto_now or auto_now_add).
created = soup.co_ment_text.created.renderContents()
modified = soup.co_ment_text.modified.renderContents()
cursor = connection.cursor()
cursor.execute("UPDATE cm_textversion SET created = %s, modified = %s WHERE id = %s", [created, modified, text.last_text_version_id])
cursor.execute("UPDATE cm_text SET created = %s, modified = %s WHERE id = %s", [created, modified, text.id])
transaction.commit_unless_managed()
# Process comments.
if soup.co_ment_text.comments:
comments_ids_map = {}
all_comments = soup.co_ment_text.comments.findAll('comment')
# Sort by id in order to have parent processed before reply_to
for imported_comment in sorted(all_comments, key=lambda cid: cid.id.renderContents()):
# Creates each comment.
comment = Comment.objects.create(
text_version=text.get_latest_version(),
title=imported_comment.title.renderContents(),
state=imported_comment.state.renderContents(),
name=imported_comment.find('name').renderContents(),
email=imported_comment.email.renderContents(),
format=imported_comment.format.renderContents(),
content=re.sub(r'^<!\[CDATA\[|\]\]>$', '', imported_comment.content.renderContents()),
content_html=re.sub(r'^<!\[CDATA\[|\]\]>$', '', imported_comment.content_html.renderContents()),
)
# Stores id for reply_to mapping.
comments_ids_map[imported_comment.id.renderContents()] = comment
# Process boolean and potentially null integer/foreign key attributes.
save = False
if imported_comment.deleted.renderContents() == 'True':
comment.deleted = True
save = True
if imported_comment.start_wrapper.renderContents() != 'None':
comment.start_wrapper = imported_comment.start_wrapper.renderContents()
save = True
if imported_comment.end_wrapper.renderContents() != 'None':
comment.end_wrapper = imported_comment.end_wrapper.renderContents()
save = True
if imported_comment.start_offset.renderContents() != 'None':
comment.start_offset = imported_comment.start_offset.renderContents()
save = True
if imported_comment.end_offset.renderContents() != 'None':
comment.end_offset = imported_comment.end_offset.renderContents()
save = True
if imported_comment.find('parent'):
comment.reply_to = comments_ids_map.get(imported_comment.find('parent').renderContents())
save = True
if save:
comment.save()
# Brute updates of dates (cannot do it through django models since fields are set with auto_now or auto_now_add).
created=imported_comment.created.renderContents(),
modified=imported_comment.modified.renderContents(),
cursor.execute("UPDATE cm_comment SET created = %s, modified = %s WHERE id = %s", [created, modified, comment.id])
transaction.commit_unless_managed()
# Logs on activity.
register_activity(request, "text_imported", text)
display_message(request, _(u'Text "%(text_title)s" has been imported')%{"text_title":text.get_latest_version().title})
return text, HttpResponseRedirect(reverse('text-view', args=[text.key]))
else:
form = createForm()
return None, render_to_response('site/text_create_import.html', {'form' : form}, context_instance=RequestContext(request))
@has_global_perm('can_create_text')
def text_create_upload(request):
text, rep = _text_create_upload(request, CreateTextUploadForm)
return rep
def text_create_import(request):
text, rep = _text_create_import(request, CreateTextImportForm)
return rep
def create_text(user, data):
text = Text.objects.create_text(title=data['title'],
format=data['format'],
content=data['content'],
note=data.get('note', None),
name=data.get('name', None),
email=data.get('email', None),
tags=data.get('tags', None),
user=user
)
text.update_denorm_fields()
text_version = text.get_latest_version()
text_content = text_version.content
for attach_file in data.get('attachs', []):
attach_data = file(attach_file, 'rb').read()
filename = os.path.basename(attach_file)
attachment = Attachment.objects.create_attachment(filename=filename, data=attach_data, text_version=text_version)
attach_url = reverse('text-attach', args=[text.key, attachment.key])
text_content = text_content.replace(filename, attach_url)
# save updated (attach links) text content
text_version.content = text_content
text_version.save()
return text